昔(Java1.4の頃)はJavaでThreadを使おうとすると、Threadを継承したクラスを作ったりRunnableを実装したクラスを作ったりして、直接Threadを使っていました。
しかし、Java5からはExecutorフレームワークという便利なものが作られたようで、最近は直接Threadを使うのではなくExecutorフレームワークを使うのが普通なようです。
最近できたフレームワークらしく実装そのものは簡単なのですが、例外処理でちょっと調査が必要になったのでそのへんを備忘録としてまとめておきます。
Executor#executeを使う場合
スレッドを実行した結果の戻り値が必要ない場合は、executeメソッドを使います。この場合、タスクを渡したexecuteメソッドから例外が投げられるようです。
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class MainForExecute {
public static void main(String[] args) throws Exception {
var pool = Executors.newWorkStealingPool();
try {
for (int i = 0; i < 20; i++) {
try {
// 例外はexecuteメソッドで投げられる
pool.execute(new Task());
} catch (Exception e) {
e.printStackTrace();
}
}
} finally {
pool.shutdown();
// すべてのスレットが終了するか、指定時間経過するまで待つ
pool.awaitTermination(1, TimeUnit.MINUTES);
}
}
private static class Task implements Runnable {
@Override
public void run(){
var random = new Random();
int result = random.nextInt(100);
System.out.println(Thread.currentThread().getName() + " : " + result);
if (result < 30) {
// runメソッドは検査例外を投げられないので非検査例外を投げる
throw new RuntimeException("result is " + result);
}
}
}
}
マルチスレッドではなくシングルスレッドで処理を行っているように見えますが、実行結果を見るとちゃんと複数のスレッドで処理が行われているようです。
また、複数の例外が発生した場合もそれぞれのスタックトレースが表示されます。
ExecutorService#submitを使う場合
スレッドの実行結果が必要な場合はsubmitメソッドを利用します。このメソッドは戻り値としてFutureのインスタンスを返すのですが、Futureインスタンスから値を取得するタイミングで例外を投げる様になっています。
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class MainForSubmit {
public static void main(String[] args) throws Exception{
var pool = Executors.newWorkStealingPool();
try {
var list = new ArrayList<Future<Integer>>();
for (int i = 0; i < 20; i++) {
// 実行結果をリストに格納
var future = pool.submit(new MyTask());
list.add(future);
}
// すべての実行結果を表示
for (Future<Integer> result : list) {
try {
// 発生した例外はgetメソッドを実行したときに投げられる
var value = result.get();
System.out.println("result : " + value);
} catch (ExecutionException ee) {
ee.getCause().printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
} finally {
pool.shutdown();
// すべてのスレットが終了するか、指定時間経過するまで待つ
pool.awaitTermination(1, TimeUnit.MINUTES);
}
}
private static class MyTask implements Callable<Integer> {
@Override
public Integer call() throws Exception{
var random = new Random();
int result = random.nextInt(100);
if (result < 30) {
// callは検査例外を投げられる
throw new Exception("result is " + result);
}
return result;
}
}
}
一度実行結果をリストに格納後、すべてのFutureインスタンスからgetメソッドで値を取り出しています。getを実行する際、Taskのcallメソッドで例外が発生していたらExecutionExceptionが投げられる仕組みです。
executeを使う場合よりコードが複雑ではありますが、私としてはsubmitを使うほうが理解しやすかったです。executeはなんでこのコードでマルチスレッドなのか、納得ができていません・・・。
コメント