ExecutorService、すべてのタスクが完了するまで待機する方法


196

すべてのタスクが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実行可能を実装します。これはタスクを正しく実行するように見えますが、コードはでクラッシュwait()IllegalMonitorStateExceptionます。これは奇妙です。私がおもちゃの例をいじってみたところ、動作するように見えました。

uniquePhrases数万の要素が含まれています。別の方法を使用する必要がありますか?できるだけシンプルなものを探しています


1
待機を使用したい場合(答えはそうではないことがわかります):待機する場合は常にオブジェクト(この場合はes)で同期する必要があります
mihi

13
ThreadPoolを初期化するより良い方法はExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getRuntime()。availableProcessors();
Vik Gamov、2011年

:[この] [1]は興味深い代替.. [1]であるstackoverflow.com/questions/1250643/...
はRazvan Petruescu

1
CountDownLatchを使用する場合、これはサンプルコードです:stackoverflow.com/a/44127101/4069305
Tuan Pham

回答:


213

最も簡単なアプローチは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は、Futurenullを返すsの束が含まれます(の定義を参照してくださいExecutors.callable()。おそらく、やりたいことはわずかなリファクタリングなので、有用な回答を返すか、基になるへの参照を取得できますがComputeDTask、あなたの例からは分からない。

明確でない場合は、invokeAll()すべてのタスクが完了するまでは戻りません。(つまり、コレクションFuture内のすべてのが要求された場合answersに報告します.isDone()。)これにより、手動によるシャットダウン、awaitTerminationなどがすべて回避されExecutorService、必要に応じて、これを複数のサイクルできれいに再利用できます。

SOにはいくつかの関連する質問があります。

これらはどれも厳密にあなたの質問に的を射ているものではありませんが、人々がどのように使用するべきであると考えるExecutor/ ExecutorServiceすべきかについて少し色を提供します。


9
これは、すべてのジョブをバッチで追加し、Callablesのリストにぶら下がっている場合に最適ですが、コールバックまたはイベントループの状況でExecutorService.submit()を呼び出している場合は機能しません。
Desty

2
ExecutorServiceが不要になった場合でもshutdown()を呼び出す必要があることに言及する価値があると思います。そうしないと、スレッドが終了しません(corePoolSize = 0またはallowCoreThreadTimeOut = trueの場合を除く)。
John29

すごい!まさに私が探していたもの。答えを教えてくれてありがとう。これを試してみましょう。
MohamedSanaulla 2017

59

すべてのタスクが完了するまで待機する場合は、shutdownではなくメソッドを使用してくださいwait。その後、それに従ってくださいawaitTermination

また、を使用Runtime.availableProcessorsしてハードウェアスレッドの数を取得できるため、スレッドプールを適切に初期化できます。


27
shutdown()は、ExecutorServiceが新しいタスクを受け入れないようにし、アイドル状態のワーカースレッドを閉じます。シャットダウンの完了を待機するように指定されておらず、ThreadPoolExecutorの実装は待機しません。
Alain O'Dea 2010

1
@Alain-ありがとう。私はawaitTerminationについて言及すべきだった。修繕。
NG。

5
タスクが完了するために、さらにタスクをスケジュールする必要がある場合はどうなりますか?たとえば、ブランチをワーカースレッドに渡すマルチスレッドツリートラバーサルを作成できます。その場合、ExecutorServiceは即座にシャットダウンされるため、再帰的にスケジュールされたジョブを受け入れることができません。
ブライアンゴードン

2
awaitTerminationパラメータとしてタイムアウト時間が必要です。有限の時間を提供し、その周りにループを配置してすべてのスレッドが終了するまで待機することは可能ですが、もっとエレガントな解決策があるかどうか疑問に思っていました。
Abhishek S

1
あなたは正しいですが、この答えを見てください-stackoverflow.com/a/1250655/263895-信じられないほど長いタイムアウトを常に与えることができます
NG。

48

内のすべてのタスクを待っている場合はExecutorService仕上げにすることは、正確にあなたの目標ではなく、むしろまで待っているタスクの特定のバッチが完了している、あなたが使用することができますCompletionService-具体的には、ExecutorCompletionService

アイデアは、作成することでExecutorCompletionService、あなたのラップExecutor提出するいくつかの既知の数によってタスクのをCompletionService、その後、その描き同じ番号からの結果の完了キューのいずれか使用してtake()(どのブロック)かpoll()(しません)。送信したタスクに対応する期待される結果をすべて取得したら、すべて完了したことがわかります。

インターフェースからは明らかではないので、もう一度これを述べましょうCompletionService。描画しようとするものの数を知るためには、何を入れるかを知っている必要があります。これは特にtake()メソッドで重要です。一度に呼び出しすぎると、他のスレッドが同じに別のジョブを送信するまで、呼び出しスレッドをブロックしますCompletionService

Java Concurrency in Practice』には、使用方法を示すいくつかの例CompletionServiceがあります。


これは私の答えの良い対置点です-質問に対する簡単な答えはinvokeAll()だと思います。しかし、@ sehは、ESにジョブのグループを送信し、それらが完了するのを待つときに適切です... --JA
andersoj

@ om-nom-nom、リンクを更新していただきありがとうございます。私は答えがまだ有用であることを見てうれしいです。
2013

1
良い答え、私は知らなかったCompletionService
Vic

1
これは、既存のExecutorServiceをシャットダウンしたくないが、タスクのバッチを送信して、すべてのタスクがいつ完了するかを知りたい場合に使用するアプローチです。
ToolmakerSteve

11

あなたが実行を終了するエグゼキュータのサービスを待ちたい場合は、コールshutdown()してから、awaitTermination(単位、UNITTYPE) 、例えばawaitTermination(1, MINUTE)。ExecutorServiceは独自のモニターでブロックしないため、使用できませんwait


私はそれがawaitTerminationだと思います。
NG。

@SB-ありがとう-私の記憶は間違いだと思います!名前を更新し、確実にリンクを追加しました。
mdma 2010

「永久に」待つには、awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com
a / 1250655/32453の

これが最も簡単なアプローチだと思います
シャービンアスガリ

1
@MosheElisha、よろしいですか?docs.oracle.com/javase/8/docs/api/java/util/concurrent/…、以前に送信されたタスクが実行される
Jaime Hablutzel、

7

特定の間隔でジョブが完了するのを待つことができます。

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を使用することもできます。submitRunnable)を実行し、それが返す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は、適切に処理するために非常に重要です。それはあなたやあなたのライブラリのユーザーが長いプロセスを安全に終了させるものです。


6

使うだけ

latch = new CountDownLatch(noThreads)

各スレッドで

latch.countDown();

そして障壁として

latch.await();

6

IllegalMonitorStateExceptionの根本的な原因:

スレッドがオブジェクトのモニターで待機しようとしたか、指定されたモニターを所有せずにオブジェクトのモニターで待機している他のスレッドに通知しようとしたことを示すためにスローされます。

コードから、ロックを所有せずにExecutorServiceでwait()を呼び出しました。

以下のコードで修正されます IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

以下のいずれかの方法に従って、に送信されたすべてのタスクの完了を待ちExecutorServiceます。

  1. すべてを繰り返し処理FutureからタスクsubmitへのExecutorService呼び出しをブロックすると、ステータスチェックget()Futureオブジェクト

  2. invokeAllを使用するExecutorService

  3. CountDownLatchの使用

  4. 使用ForkJoinPool又はnewWorkStealingPoolのをExecutors(Javaの8以降)

  5. Oracleドキュメントページで推奨されているとおりにプールをシャットダウンします。

    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)) {

    aはwhile(condition)1分ごとにチェックします。


6

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";
        }
    }
}

3

また、クロールする一連のドキュメントがあるという状況もあります。まず、処理する必要のある最初の「シード」ドキュメントから始めます。そのドキュメントには、処理する必要のある他のドキュメントへのリンクが含まれています。

私のメインプログラムでは、次のように記述してCrawler、一連のスレッドを制御します。

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

ツリーをナビゲートしたい場合も同じ状況になります。ルートノードをポップし、各ノードのプロセッサが必要に応じて子をキューに追加し、スレッドの束がツリー内のすべてのノードを処理します。

少し意外と思ったものをJVMで見つけることができませんでした。したがってThreadPool、ドメインに適したメソッドを追加するために直接またはサブクラスを使用できるクラスを作成しましたschedule(Document)。それが役に立てば幸い!

ThreadPool Javadoc | メイベン


Doc Linkが死んでいる
Manti_Core

@Manti_Core-ありがとう、更新されました。
エイドリアン・スミス

2

コレクション内のすべてのスレッドを追加し、を使用して送信しinvokeAllます。のinvokeAllメソッドを使用できる場合ExecutorService、JVMはすべてのスレッドが完了するまで次の行に進みません。

ここに良い例があります: ExecutorServiceを介したinvokeAll


1

タスクを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



0

タスクを完了するのに適していると思われる指定されたタイムアウトで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.");
    }

0

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、メソッドがブロックし、呼び出し側のスレッドを使用してタスクを実行し、実際に空になったときに戻るところにあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.