JavaでのExecutorService
の動作について基本的な質問があります。
単純にThreads
を作成していくつかのタスクを並行して実行することと、各タスクをThreadPool
に割り当てることの違いを理解するのは非常に困難です。
ExecutorService
も非常にシンプルで効率的に使用できるように見えるので、なぜそれをいつも使用しないのか疑問に思いました。
それは単に他の方法よりも速くジョブを実行することの問題ですか?
2つの方法の違いを示す2つの非常に単純な例を次に示します。
executorサービスの使用:Hello World(タスク)
static class HelloTask implements Runnable {
String msg;
public HelloTask(String msg) {
this.msg = msg;
}
public void run() {
long id = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + id);
}
}
executorサービスの使用:Hello World(executorの作成、送信)
static class HelloTask {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
for (int i=0; i<ntasks; i++) {
HelloTask t = new HelloTask("Hello from task " + i);
exs.submit(t);
}
exs.shutdown();
}
}
以下は、同様の例を示していますが、Callableインターフェースを拡張しています。2つの違いを教えてください。どちらの場合に、一方を他方の代わりに使用すべきですか。
executorサービスの使用:カウンター(タスク)
static class HelloTaskRet implements Callable<Long> {
String msg;
public HelloTaskRet(String msg) {
this.msg = msg; }
public Long call() {
long tid = Thread.currentThread().getId();
System.out.println(msg + " from thread:" + tid);
return tid;
}
}
executorサービスの使用:(作成、送信)
static class HelloTaskRet {
public static void main(String[] args) {
int ntasks = 1000;
ExecutorService exs = Executors.newFixedThreadPool(4);
Future<Long>[] futures = (Future<Long>[]) new Future[ntasks];
for (int i=0; i<ntasks; i++) {
HelloTaskRet t = new HelloTaskRet("Hello from task " + i);
futures[i] = exs.submit(t);
}
exs.shutdown();
}
}
質問とサンプルコードは相関していませんが、両方を明確にしてみます。スレッドを無計画に生成することに対するExecutorService
の利点は、スレッドが予測どおりに動作し、JVMで比較的大きなスレッド作成のオーバーヘッドを回避できることです(たとえば、スレッドごとにメモリを予約する必要があります)。予測可能性によって、少なくともfixedThreadPool
については、同時スレッドの最大数がわかっており、それらがいつどのように作成されるかがわかっている(突然のピーク時にJVMが爆発しないようにする) 。
Vince Emigh:
ExecutorService
は、最大値がないcachedThreadPool
もサポートします。人々がExecutorService
を使用することを選択する主な理由は、(worker threadsを使用して)複数のスレッドを作成するオーバーヘッドを防ぐためです。主に、多くの小さなタスクを別のスレッドで実行する必要がある場合に使用されます。また、singleThreadExecutor
についても忘れないでください。
ここで、Runnable
とCallable
のトピックについて、例から簡単に理解できます。 Callable
sは値のプレースホルダー(Future
)を返すことができ、将来的に実際の値が入力されます。 Runnable
sは何も返すことができません。
By Vince Emigh:
Runnable
も例外をスローできませんが、Callable
は例外をスローできます。
ExecutorService は、プレーンスレッドと比較して多くの利点を提供します
単一のスレッドでも、Executors.newFixedThreadPool(1);
を使用することを好みます
関連するSEの質問をご覧ください。