web-dev-qa-db-ja.com

ExecutorService、すべてのタスクが完了するのを待つ方法

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には、数万の要素が含まれています。別の方法を使用する必要がありますか?できるだけシンプルなものを探しています

175
george smiley

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

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

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

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

197
andersoj

すべてのタスクが完了するまで待機する場合は、shutdownの代わりに wait メソッドを使用します。その後に awaitTermination を続けます。

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

54
NG.

ExecutorService内のすべてのタスクの完了を待つことが正確な目標ではなく、特定のタスクのバッチが完了するまで待つ場合は、 CompletionService —具体的には ExecutorCompletionService

アイデアは、ExecutorCompletionServiceをラップするExecutorを作成し、 submit someknown numberを介してCompletionServiceを介してタスクを作成し、次に完了キューから完了キューの結果を描画することです take() (ブロックする)または poll() (ブロックしない)。送信したタスクに対応する期待される結果をすべて描画したら、すべて完了していることがわかります。

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

本には CompletionService の使用方法を示すいくつかの例があります Java実際の並行性

48
seh

Executorサービスの実行が完了するのを待つ場合は、shutdown()を呼び出してから、 awaitTermination(units、unitType) 、たとえばawaitTermination(1, MINUTE)。 ExecutorServiceは独自のモニターでブロックしないため、waitなどを使用できません。

10
mdma

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

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

7
Alain O'Dea

ただ使う

latch = new CountDownLatch(noThreads)

各スレッドで

latch.countDown();

そして障壁として

latch.await();
6
J. Ruhe

IllegalMonitorStateException の根本原因:

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

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

以下のコードはIllegalMonitorStateExceptionを修正します

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

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

  1. FuturesubmitからすべてのExecutorServiceタスクを反復処理し、Futureオブジェクトのget()ブロック呼び出しでステータスを確認します

  2. ExecutorServiceinvokeAll を使用する

  3. CountDownLatch の使用

  4. ForkJoinPool または newWorkStealingPool of Executors(Java 8以降)の使用

  5. 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).

5
Ravindra babu

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";
        }
    }
}
4
Nitin Vavdiya

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

私のメインプログラムでは、Crawlerが多数のスレッドを制御する次のようなものを書きたいだけです。

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

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

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

AutoStopThreadPool Javadoc | ダウンロード

3
Adrian Smith

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

ここに良い例があります: InvokeAll via ExecutorService

2
zgormez

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

1
Matej Tymes

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
punkers

これに代わる簡単な方法は、結合とともにスレッドを使用することです。参照: スレッドの結合

0
Vicky Kapadia

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空のときに戻ります。

0
adib