ExecutorService
のすべてのタスクが完了するのを待つ最も簡単な方法は何ですか?私のタスクは主に計算であるため、多数のジョブ(コアごとに1つ)を実行したいだけです。現在、私のセットアップは次のようになっています。
ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {
es.execute(new ComputeDTask(singleTable));
}
try{
es.wait();
}
catch (InterruptedException e){
e.printStackTrace();
}
ComputeDTask
はrunnableを実装します。これはタスクを正しく実行するように見えますが、コードはwait()
でIllegalMonitorStateException
でクラッシュします。これは奇妙なことです。なぜなら、私はいくつかのおもちゃの例で遊んでみて、うまくいくように見えたからです。
uniquePhrases
には、数万の要素が含まれています。別の方法を使用する必要がありますか?できるだけシンプルなものを探しています
最も簡単なアプローチは、 ExecutorService.invokeAll()
を使用することです。これは、ワンライナーで必要なことを行います。あなたの用語では、ComputeDTask
を変更またはラップしてCallable<>
を実装する必要があります。これにより、柔軟性が大幅に向上します。おそらくアプリにはCallable.call()
の意味のある実装がありますが、 Executors.callable()
を使用しない場合はラップする方法があります。
ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());
for (DataTable singleTable: uniquePhrases) {
todo.add(Executors.callable(new ComputeDTask(singleTable)));
}
List<Future<Object>> answers = es.invokeAll(todo);
他の人が指摘したように、必要に応じてinvokeAll()
のタイムアウトバージョンを使用できます。この例では、answers
には、nullを返すFuture
sの束が含まれます(Executors.callable()
の定義を参照してください。おそらく、やりたいことは少しリファクタリングすることで、有用な回答を返したり、基になるComputeDTask
への参照を取得したりできます。しかし、あなたの例からはわかりません。
明確でない場合は、すべてのタスクが完了するまでinvokeAll()
が返されないことに注意してください。 (つまり、Future
コレクション内のすべてのanswers
sは、要求された場合、.isDone()
を報告します。)これにより、手動シャットダウン、awaitTerminationなどがすべて回避され、必要に応じてこのExecutorService
を複数のサイクルできれいに再利用できます。
SOにはいくつかの関連する質問があります。
これらはどれも、あなたの質問に厳密には当てはまりませんが、Executor
/ExecutorService
をどのように使用すべきかを人々がどのように考えているかについて、少し色を付けています。
すべてのタスクが完了するまで待機する場合は、shutdown
の代わりに wait
メソッドを使用します。その後に awaitTermination
を続けます。
また、 Runtime.availableProcessors
を使用してハードウェアスレッドの数を取得し、スレッドプールを適切に初期化できます。
ExecutorService
内のすべてのタスクの完了を待つことが正確な目標ではなく、特定のタスクのバッチが完了するまで待つ場合は、 CompletionService
—具体的には ExecutorCompletionService
。
アイデアは、ExecutorCompletionService
をラップするExecutor
を作成し、 submit someknown numberを介してCompletionService
を介してタスクを作成し、次に完了キューから完了キューの結果を描画することです take()
(ブロックする)または poll()
(ブロックしない)。送信したタスクに対応する期待される結果をすべて描画したら、すべて完了していることがわかります。
インターフェースからは明らかではないので、これをもう一度述べてみましょう。どれだけ多くのものを引き出すかを知るために、CompletionService
にどれだけ多くのものを入れるかを知る必要があります。これは、特にtake()
メソッドで重要です。一度呼び出すと、他のスレッドが同じCompletionService
に別のジョブを送信するまで、呼び出しスレッドをブロックします。
本には CompletionService
の使用方法を示すいくつかの例があります Java実際の並行性 。
Executorサービスの実行が完了するのを待つ場合は、shutdown()
を呼び出してから、 awaitTermination(units、unitType) 、たとえばawaitTermination(1, MINUTE)
。 ExecutorServiceは独自のモニターでブロックしないため、wait
などを使用できません。
特定の間隔でジョブが終了するのを待つことができます。
int maxSecondsPerComputeDTask = 20;
try {
while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
// consider giving up with a 'break' statement under certain conditions
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
または、ExecutorServiceを使用することもできます。submit(Runnable)そしてFutureオブジェクトを収集し、それが返されるのを待ってget()を順番に呼び出します終わります。
ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
InterruptedExceptionは、適切に処理するために非常に重要です。それはあなたやあなたのライブラリのユーザーが長いプロセスを安全に終了させるものです。
ただ使う
latch = new CountDownLatch(noThreads)
各スレッドで
latch.countDown();
そして障壁として
latch.await();
IllegalMonitorStateException の根本原因:
スレッドがオブジェクトのモニターで待機しようとしたこと、または指定されたモニターを所有せずにオブジェクトのモニターで待機している他のスレッドに通知しようとしたことを示すためにスローされます。
コードから、ロックを所有せずにExecutorServiceでwait()を呼び出しました。
以下のコードはIllegalMonitorStateException
を修正します
try
{
synchronized(es){
es.wait(); // Add some condition before you call wait()
}
}
以下のいずれかの方法に従って、ExecutorService
に送信されたすべてのタスクの完了を待ちます。
Future
のsubmit
からすべてのExecutorService
タスクを反復処理し、Future
オブジェクトのget()
ブロック呼び出しでステータスを確認します
ExecutorService
で invokeAll を使用する
CountDownLatch の使用
ForkJoinPool または newWorkStealingPool of Executors
(Java 8以降)の使用
Oracleのドキュメントで推奨されているようにプールをシャットダウンします page
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
オプション1から4の代わりにオプション5を使用しているときに、すべてのタスクが完了するまで正常に待機する場合は、変更します
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
に
1分ごとにチェックするwhile(condition)
.
ExecutorService.invokeAll
メソッドを使用できます。すべてのタスクを実行し、すべてのスレッドがタスクを完了するまで待機します。
これで完了です javadoc
このメソッドのオーバーロードバージョンを使用して、タイムアウトを指定することもできます。
ExecutorService.invokeAll
を使用したサンプルコードを次に示します。
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(3);
List<Callable<String>> taskList = new ArrayList<>();
taskList.add(new Task1());
taskList.add(new Task2());
List<Future<String>> results = service.invokeAll(taskList);
for (Future<String> f : results) {
System.out.println(f.get());
}
}
}
class Task1 implements Callable<String> {
@Override
public String call() throws Exception {
try {
Thread.sleep(2000);
return "Task 1 done";
} catch (Exception e) {
e.printStackTrace();
return " error in task1";
}
}
}
class Task2 implements Callable<String> {
@Override
public String call() throws Exception {
try {
Thread.sleep(3000);
return "Task 2 done";
} catch (Exception e) {
e.printStackTrace();
return " error in task2";
}
}
}
また、クロールするドキュメントのセットがあるという状況もあります。処理する必要がある最初の「シード」ドキュメントから開始します。このドキュメントには、処理する必要がある他のドキュメントへのリンクが含まれています。
私のメインプログラムでは、Crawler
が多数のスレッドを制御する次のようなものを書きたいだけです。
Crawler c = new Crawler();
c.schedule(seedDocument);
c.waitUntilCompletion()
ツリーをナビゲートしたい場合も同じ状況が発生します。ルートノードにポップし、各ノードのプロセッサが必要に応じてキューに子を追加し、スレッドの束がそれ以上なくなるまでツリー内のすべてのノードを処理します。
少し驚くべきことだと思ったJVMには何も見つかりませんでした。そこで、ドメインに適したメソッドを追加するために直接使用するかサブクラスを使用できるクラスAutoStopThreadPool
を作成しました。 schedule(Document)
。それが役に立てば幸い!
コレクションにすべてのスレッドを追加し、invokeAll
を使用して送信します。 invokeAll
のExecutorService
メソッドを使用できる場合、JVMはすべてのスレッドが完了するまで次の行に進みません。
ここに良い例があります: InvokeAll via ExecutorService
タスクをRunnerに送信し、メソッドの呼び出しを待機しますwaitTillDone() このような:
Runner runner = Runner.runner(2);
for (DataTable singleTable : uniquePhrases) {
runner.run(new ComputeDTask(singleTable));
}
// blocks until all tasks are finished (or failed)
runner.waitTillDone();
runner.shutdown();
それを使用するには、このgradle/maven依存関係を追加します:'com.github.matejtymes:javafixes:1.0'
詳細については、こちらをご覧ください: https://github.com/MatejTymes/JavaFixes またはこちら: http://matejtymes.blogspot.com/2016/04/executor-that-notifies -you-when-task.html
Executorがタスクの完了に適していると思われる指定されたタイムアウトで終了するのを待ちます。
try {
//do stuff here
exe.execute(thread);
} finally {
exe.shutdown();
}
boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
if (!result)
{
LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
}
これに代わる簡単な方法は、結合とともにスレッドを使用することです。参照: スレッドの結合
ForkJoinPool
が必要なように思えますが、グローバルプールを使用してタスクを実行します。
public static void main(String[] args) {
// the default `commonPool` should be sufficient for many cases.
ForkJoinPool pool = ForkJoinPool.commonPool();
// The root of your task that may spawn other tasks.
// Make sure it submits the additional tasks to the same executor that it is in.
Runnable rootTask = new YourTask(pool);
pool.execute(rootTask);
pool.awaitQuiescence(...);
// that's it.
}
美しさはpool.awaitQuiescence
にあり、メソッドは呼び出し元のスレッドを利用してタスクを実行し、really空のときに戻ります。