情報科学屋さんを目指す人のメモ

方法・手順・解説を書き残すブログ。私と同じことを繰り返さずに済むように。

Java:forループの独立した処理を並列化する方法メモ

Java (37)

forループの中身を複数スレッドで並列処理したいときの書き方をメモしておきます。

対象

たとえば、複数スレッドに並列処理させたいforループが次のようなコードだったとします。

for (Node node : nodes) {
	node.process()
}

processメソッドが同時に実行されても問題ない、nodeオブジェクトごとに独立した処理だと考えます。

方法

次のように書き換えます。強調表示している行に、変更前のコードが残るようにしてあります(微妙に違う部分有り注意)。

ExecutorService threadPool = Executors.newFixedThreadPool(8);

Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
for (Node node0 : nodes) {
	final Node node = node0;
	processes.add(new Callable<Void>() {
		@Override
		public Void call() {
			node.process();
			return null;
		}
	});
}

try {
	threadPool.invokeAll(processes);
} catch (InterruptedException e) {
	throw new RuntimeException(e);
} finally {
	threadPool.shutdown();
}

以下、補足説明を書いておきます。

Executors.newFixedThreadPool(8);

ここで、スレッドプールを作成します。ここで指定した数(ここでは8つ)のスレッドが利用されます。

※「Runtime.getRuntime().availableProcessors();」でコア数(論理)を取得できるので、これを使って設定してみてください。

Collection> processes = new LinkedList>();

このあと、並列に実行される「処理」の1つ1つを「Callable<Void>」型のオブジェクトにします。

そのオブジェクトを格納しておくCollection型のオブジェクトが必要なので、ここで作成しています。

final Node node = node0;

なぜわざわざfinalの変数に格納し直しているかというと、この後に定義するcall()メソッドの中で、finalがついていない変数を参照することができないからです(Java 7の時点ではクロージャに対応していないため)。

「Cannot refer to a non-final variable node0 inside an inner class defined in a different method」というエラーメッセージが表示されてしまいます。

また、「for (final Node node : nodes)」のように書いても大丈夫ですが、見間違い防止のために、とりあえず変更前のコードはそのまま残すようにしました。

new Callable<Void>() {...}

ここで、Callable<Void>型を継承した無名クラスのオブジェクトを作成しています。1回しか使わないクラスを、newと同時に定義している感じです。

今回の処理専用にCallable<Void>を継承したクラスを作成しておいても良いのですが、無名クラスという仕組みを使うことで、手軽に書けています。

return null;

return null;は省略不可です。Eclipseの場合は「This method must return a result of type Void」と赤線が引かれてしまいます。

threadPool.invokeAll(processes);

このinvokeAllメソッドで、用意しておいた「処理」たちを、スレッドプールのスレッドたちに処理させています。スレッド数の上限はあらかじめ指定してあるので、その数のスレッドに処理が割り当てられた後は、各スレッドの処理が終わり次第、次の処理を読み込んで実行していきます。

スレッドプールのメソッドにはいろいろあるのですが、そのうち今回使ったinvokeAllのこのメソッドは、すべての処理が終了するまでブロックし続けます。他には、ブロックせずにFutureオブジェクトで処理結果を後から取得できたり、タイムアウトを設定できる呼び出し方(メソッド)もあります。

threadPool.shutdown();

このままだと処理された後も、スレッドは破棄されません。shutdown()メソッドを呼ぶことで、スレッドプールにあった全スレッドが破棄されます。

これを呼ばなければ、スレッドプールのthreadたちは、再利用のために残り続けます。

参考

コメント(0)

新しいコメントを投稿