2012年04月04日

java.util.concurrent 〜Executor その1〜


今日からはExecutorインターフェイス関連をやっていきます。Executorは非同期処理を実行するためのインターフェイスです。java.util.concurrentで一番重要そうなインターフェイスですね。まずは超簡単なサンプルから見ていきます。

ExecutorとRunnable
最初はExecutorとRunnableを用いた単純なサンプルから。
public static void main(String[] args) {

	//実行させるタスクをRunnableで作成
	Runnable task = new Runnable(){

		@Override
		public void run() {
			//三秒待つ(java.util.concurrent.TimeUnitを使用)
			try {
				TimeUnit.MILLISECONDS.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Hello java.util.concurrent World");
		}

	};

	//シングルスレッドを生成するためのExcutorを作成する
	Executor executor = Executors.newSingleThreadExecutor();

	//タスクを実行
	executor.execute(task);

	//※注意
	//Executorインターフェースはタスク終了後もスレッドを上げ続けるので、このままだと
	//Mainスレッドは終わりません。

}

・非同期処理の作成
まずはRunnableを匿名クラスで生成して、非同期のタスクを作っています。内容は3秒待ってから「Hello java.util.concurrent World」と出力するだけです。

・Executorを作成
続いて主役であるExecutorを作成します。といっても、Executorはインターフェイスなので、Executorsというクラスに作成してもらいます。このExecutorsは後述するExecutorService、ScheduledExecutorService、ThreadFactory、および Callableを作成するためのファクトリです。ちなみにExecutorはExecutorServiceやScheduledExecutorServiceのスーパーインターフェイスです。Executorsは用途によって様々なメソッドが用意されていますが、今回は単純に「1つのスレッドを実行するExcutorService」を作成するExecutors.newSingleThreadExecutor()を使用します。

・タスクを実行
Executor.execute()を呼び出し、引数にRunnableを渡してやります。ここで気をつけて欲しいのはこのままだとmainスレッドは終了しないということです。コメントにも記しましたがExecutorインターフェースはタスク終了後もスレッドを上げ続けるからです。じゃあどうするか?というと、Executorではどうすることもできません。実はExecutorはexecuteというメソッドしか持っていないからです。この問題に対処するにはExecutorServiceが必要になってきます。

ExecutorServiceとRunnable
次はExecutorServiceを試してみます。ExecutorServiceはExecutorの拡張インターフェイスです。
public static void main(String[] args) {

	//実行させるタスクをRunnableで作成
	Runnable task = new Runnable(){

		@Override
		public void run() {
			//三秒待つ(java.util.concurrent.TimeUnitを使用)
			try {
				TimeUnit.MILLISECONDS.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("Hello java.util.concurrent World");
		}

	};

	//シングルスレッドを生成するためのExecutorServiceを作成する
	ExecutorService executor = Executors.newSingleThreadExecutor();

	//タスクを実行
	executor.execute(task);

	System.out.println(executor.isShutdown());

	//Excutorの処理が終了したらスレッドを終了させる命令
	executor.shutdown();

	//仮にこの時点でスレッドが終了していなくてもshutdownが呼ばれていればisShutdownの結果はtrue
	//「起動したスレッドが終了しているか」はisTerminated()で検査できる
	System.out.println(executor.isShutdown());
}

Executors.newSingleThreadExecutor()を呼び出し、execute()で実行するまでは全く前回と同じです。ただし受け取りがExecutorServiceなっていることに注意。
・非同期処理スレッドの終了
executor.shutdown()で非同期処理が終了したらスレッドをシャットダウンさせることができます。非同期処理がいつ終わるか判らないことを考えると「シャットダウン予約」のようなニュアンスでしょうか。コメントにあるとおり、「非同期処理が終了し、スレッドがシャットダウンされたかどうか」を調べるにはExecutorService.isTerminated()を使用します。

次回は非同期処理からの戻り値の取得についてです。
posted by sandman at 22:12| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

2012年03月30日

java.util.concurrent 〜CopyOnWriteArrayList〜


突然ですが、java.util.concurrent使ってますか?
僕は使ってません。また、使っている人をあんまり見たことがありません。

java.util.concurrentはJava5.0から追加された並列プログラミングの要求に応えるためのパッケージです。WEBアプリの開発者ならばほぼ間違いなく、そうでない人も結構な頻度で並列処理に注意を払わねばならない場面は多いと思います。しかしながら、前述の通り、java.util.concurrentを僕自身あまり使っていません。それはなぜか?真っ先に思いつく答えは「synchronizedで十分だから」というものでしょう。確かに、一般的な排他処理はsynchronizedで十分でしょう。しかし、明示的な排他処理は実に骨が折れる作業であるし、ソースの可読性も下がっていくことは確かです。

ただ、僕個人としてはjava.util.concurrentがなにがしか素敵な道具箱であることはわかっているものの「でもなんだかよく分からんから使いたくない」というのが正直なところです。ということで、java.util.concurrentをいろいろ試してみることにします。お勉強がてら。
初回はCopyOnWriteArrayListです。では行きます。

ArrayListがスレッドセーフでないのはよく知られています。ArrayListはリストの走査を行っている最中に要素の変更が加わるとConcurrentModificationExceptionが発生します。

以下のような場合です。

List<String> list = new ArrayList<String>();
Collections.addAll(list, "aa","bb","cc");
for(String h : list){
    System.out.println(h);
    list.remove(h);
}

実行結果は以下の通り。
aa
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at test1.Main1.main(Main1.java:16)

ちょっと注意すべきなのが、このConcurrentModificationExceptionが発生するタイミングです。感覚的にはlist.remove(h)のような気もしますが、そうではなくfor(String h : list){の部分になります。なぜなら、このConcurrentModificationExceptionをスローするのはこのコレクションの反復子、つまりIteratorだからです。

上記の例では単一スレッドですが、当然マルチスレッドでも同じことが発生します。

public class ArrayListTest {

	//test対象となるリスト。ArrayListで宣言
	private static List<String> arrayList = new ArrayList<String>();

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		//適当にいっぱい要素追加
		long time = System.currentTimeMillis();
		for(int i = 0; i < 10000; i++){
			arrayList.add("hogehoge" + i);
		}
		System.out.print("所要時間:");
		System.out.print(System.currentTimeMillis()-time);
		System.out.println("ミリ秒");

		//別スレッドでリストを走査する
		Thread th = new Thread(new Runnable(){
			@Override
			public void run() {
				for(String h : arrayList){			//ConcurrentModificationExceptionはここで発生します。
				}
			}
		});
		th.start();

		//走査中に操作するためにちょろっと待つ
		try {
			Thread.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		//操作中に削除
		arrayList.remove("hogehoge0");
		System.out.println("削除!");

	}

}

実行結果はこちら
所要時間:20ミリ秒
削除!
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at copyOnWriteArrayListTest.ArrayListTest$1.run(ArrayListTest.java:30)
at java.lang.Thread.run(Thread.java:662)

この問題を解決すべく作られたのがCopyOnWriteArrayListさんです。CopyOnWriteArrayListは配列に対しておこなう全ての操作を配列を新規コピーすることで実装しているスレッドセーフなArrayListです。では上記のプログラムのListの宣言を以下のように変更してみましょう。

private static List<String> arrayList = new CopyOnWriteArrayList<String>();

実行結果はこちら。

所要時間:285ミリ秒
削除!

おお、うまくいきました。素晴らしいです。でもちょっと気になるのが処理時間。Stringの要素を10000件挿入するのにArrayListでは20ミリ秒だったのに大して、CopyOnWriteArrayListでは285ミリ秒もかかってます。10倍以上遅い・・・?というわけでもないのです。

試しに要素の挿入数を100000に増やしてみます。

ArrayListの結果がこちら。
所要時間:184ミリ秒
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
at java.util.AbstractList$Itr.next(AbstractList.java:343)
at copyOnWriteArrayListTest.ArrayListTest$1.run(ArrayListTest.java:30)
at java.lang.Thread.run(Thread.java:662)削除!

対してCopyOnWriteArrayListがこれ
所要時間:37085ミリ秒
削除!

うへえ。超遅い。でもよく考えれば当たり前で、CopyOnWriteArrayListは「変更のたびに全体をコピーする」ものです。つまり、全体の要素数が増えれば増えるほど、また総称型の引数のサイズが大きければ大きいほど、操作の処理時間は累乗的に増えていきます。

以上より、CopyOnWriteArrayListを使用すべき場面は以下のようになるでしょう。

@排他処理が必要
A変更の操作がほとんどない
Bリストサイズがそれほど巨大でない(作成時に時間がかかるから)
C読み取りは多い

とりあえず今回はこれまで
posted by sandman at 15:29| Comment(0) | Java | このブログの読者になる | 更新情報をチェックする

2011年11月25日

DVDデータ変換

DVDからAVI形式ファイルの変換をやってみます。

手順としては
1.DVD Decrypterダウンロード&インストール

2.BatchDOO!ダウンロード&インストール

3.DVD DecrypterでFileモードで取り込み

4.BatchDOO!でVOBをaviに変換

って感じです。
では、順番にいきましょう。

1.DVD Decrypterダウンロード&インストール

以下のサイトにアクセスして、ダウンロード〜インストールまで
済ませて下さい。
DVD Decrtpterダウンロード&インストール

日本語化のやり方も載ってますが、別に英語でも問題ないかと思います。
この手順ではメンドクサイから英語版の前提で説明します。
その場合、上記リンク先サイトの「ステップ2」までやっといてください。
もしzipの解凍ツールがない場合はここからどうぞ。

2.BatchDOO!ダウンロード&インストール

以下のサイトからダウンロードしてください。
BatchDOO!ダウンロード

上記の[Download]ボタンをクリックしてダウンロードします。
「BatchDOO.zip」というファイルが取得できたはずです。
それを解凍した中にある[BATCHDOO.exe]が起動ファイルです。
起動するときはこれをダブルクリックします。 これは後で使います。
初回起動時に提供元の要らないソフトインストールを促されるので、チェックをはずしてから
OKを押しましょう。不要なものが勝手にインストールされるのでうっとおしいです。

3.DVD DecrypterでFileモードで取り込み
ドライブに変換したいDVDをセットしたら、DVD Decrypterを起動します。
メニューから[Mode]-[File]を選択します。

続いて、[Destination]をクリックし、ファイルの出力先を決めます。
ちなみに沢山ファイルが出力されるので、デスクトップとかにフォルダを作成して、
そこを指定したほうがいいです。ここでは
デスクトップ\DVD_DATA
を出力先に設定しました。

こんな感じになると思います。

DVD_conv1

したらば、下側にある絵の箇所をクリックして、取り込みを開始します。
すると、以下のように処理が始まるのでしばし待ちます。
ちなみに、完了したときに音楽が鳴るので音量を大きくしているとびっくりしますので注意(笑)

DVD_conv2

出力先のフォルダを見てみましょう。なんだか色々とファイルがありますね。
この中のVOBファイルを変換していくことになります。

DVD_conv3

ただし、VOBファイルというのは実は中身mpeg2ですので、拡張子をmp2に変更すると普通に
windows media playerで視聴できます。ただし、問題は サイズがでかいってことですね。
なので、このそれが我慢できる人はこの時点でやめてもかまいません。

4.BatchDOO!でVOBをaviに変換
BatchDOO!を起動します。するとこんな画面が出てくると思います。

DVD_conv4

まず、変換したVOBを[対象ファイル]にドロップします。複数いっぺんで大丈夫です。
次に、保存先も決定。

このソフトは素晴らしいことに、面倒な各種設定が目的に応じて自動選択できるように
なっています。ここでは[PC向け-Xvid(中画質)]を選択してみましょう。

DVD_conv5

あとは右側の[GO]ボタンを押すだけ。簡単ですね。
ただ、けっこう時間かかります。PCにもよりけりですが。まあしばらくほっときましょう。

完了すると、保存先フォルダに.aviファイルができてますね。これで変換自体は完了です!

補足

VOBが分かれていれば当然ファイルが分かれてますね・・・
結合はたぶんできるんですが、色々試しているうちにメンドクサクなっちゃいました。
簡単にはできなそう。いいじゃん連続再生すれば・・・

どうしてもやりたい人は「UniteMovie」「VirtualDubMod」が必要なので、適宜調べてみてください(なげやり)。
たぶん、flv形式とかであれば簡単なんだと思います。たぶん。

posted by sandman at 13:18| Comment(0) | その他技術ネタ | このブログの読者になる | 更新情報をチェックする