はじめに
Java初心者です.
概要
Javaで並列(並行)計算したいです.
Threadを直接操作する?方法は教わったことがあります. こんな感じ.
しかし, java.util.concurrentを使うことでもっと楽に処理できるみたいです. やってみました.
コード
ParallelTest.javaというコードを書いてみました.
8スレッド用意して, 8個のタスクを処理させます.
8個のタスクは, それぞれ処理に要する時間が異なるように設定しました.
タスク1は8秒, タスク2は7秒, タスク3は6秒, ..., タスク8は1秒かかります.
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
class ParallelTest{
public static void main(String[] args) {
int threadNumber = 8;
// 8スレッド用意
ExecutorService executor = Executors.newFixedThreadPool(threadNumber);
// 結果を入れる配列
String[] results = new String[threadNumber];
// タスクのリストを作る
List<Callable<String>> tasks = new ArrayList<Callable<String>>();
for(int i = 1; i <= threadNumber; i++){
tasks.add(new ParallelTasks(i));
}
try{
// 並列実行
List<Future<String>> futures;
try{
futures = executor.invokeAll(tasks);
} catch(InterruptedException e){
System.out.println(e);
return ;
}
System.out.println("-----------");
// 結果をresultsに入れる
for(int i = 0; i< threadNumber; i++){
try{
results[i] = (futures.get(i)).get();
}catch(Exception e){
System.out.println(e);
}
}
} finally{
// 終了
if(executor != null) executor.shutdown();
// 結果の配列の中身
for(String result : results) System.out.println(result);
}
}
}
// 並列計算する内容
class ParallelTasks implements Callable<String>{
int taskNumber;
public ParallelTasks(int taskNumber){
this.taskNumber = taskNumber;
}
@Override
public String call() throws Exception{
// sleep
// 開始が速いタスクほど時間がかかるように設定
Thread.sleep((long)(1000 * (8 - taskNumber) ));
// タスクの番号を出力
System.out.println("task " + taskNumber + " end.");
return "task " + taskNumber ;
}
}
出力結果.
タスク終了の通知は, 早く終わったタスクの順番になるので, タスクの番号の逆順になっています.
しかし, 結果の配列を呼び出すと, 元々のタスクの順番になっています.
task 8 end.
task 7 end.
task 6 end.
task 5 end.
task 4 end.
task 3 end.
task 2 end.
task 1 end.
-----------
task 1
task 2
task 3
task 4
task 5
task 6
task 7
task 8
(実行時間: *** time: 8.096693 *** )
色々やっているように見えますが,
Executors.newFixedThreadPool(threadNumber)でスレッドを用意して,
タスクのリストをinvokeAllするだけです.
結果を呼び出すときに頑張っているのがFutureです.
(futures.get(i)).get()で, i番目のタスクの処理結果を呼びます.
このとき, 処理が終わっていない場合は, 処理が終わるまで待ってくれます.
タスク1は8秒かかるように設定していますから, get()を行った時にはまだ終わっていないはずですが, 終了するまで待ってから結果の格納を行っています.
なので, 結果の配列resultsには, タスクの番号順で格納されています.
Futureは, タスクの実行状態を加味した行動をしてくれるんですね. 助かります.
Callableがよく分からないのですが, とりあえず並列計算したい内容をcall()に書いて, 返り値の型と合わせれば動きました.
感想
Java難しい.
参考文献
http://itpro.nikkeibp.co.jp/article/COLUMN/20071001/283395/
http://tree-tips.appspot.com/java/executorservice/
次に読む
Threadの詳しい話
CompleteTableFutureの話(yohhoyさんありがとうございます)
Java8の話.