すべてのスレッドがJavaでの作業を完了するまで待ちます


91

私は、Webからいくつかの情報を同時に取得し、バッファクラスの5つの異なるフィールドに入力する5つのスレッドを持つアプリケーションを作成しています。
すべてのスレッドがジョブを終了したら、バッファデータを検証してデータベースに保存する必要があります。
これを行うにはどうすればよいですか(すべてのスレッドが作業を終了したときに警告されます)?


4
Thread.joinは、問題を解決するための、かなり低レベルの非常にJava特有の方法です。さらに、スレッド APIに欠陥があるため、問題があります。結合が正常完了したかどうかはわかりません(実際のJava同時実行を参照)。CountDownLatchの使用など、より高いレベルの抽象化が好まれ、Java特有の考え方に「こだわっていない」プログラマにとってはより自然に見えるでしょう。私と議論しないでください、ダグ・リーと議論してください。)
セドリックマーティン

回答:


119

私が採用するアプローチは、ExecutorServiceを使用してスレッドのプールを管理することです。

ExecutorService es = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
    es.execute(new Runnable() { /*  your task */ });
es.shutdown();
boolean finished = es.awaitTermination(1, TimeUnit.MINUTES);
// all tasks have finished or the time has been reached.

7
@Leonidは、shutdown()がまさに行うことです。
Peter Lawrey 2013年

3
while(!es.awaitTermination(1, TimeUnit.MINUTES));
アクエリアスパワー

3
@AquariusPowerあなたはそれをもっと長く、または永遠に待つように言うことができます。
Peter Lawrey、2014

1
わかりました; ループにメッセージを追加して、すべてのスレッドの終了を待機していることを伝えました。ありがとう!
アクエリアスパワー

1
@PeterLawrey、電話する必要がありますes.shutdown();か?私は、私が使用してスレッドを実行するコード記述した場合es.execute(runnableObj_ZipMaking);tryブロックとでfinally、私はと呼ばれるがboolean finshed = es.awaitTermination(10, TimeUnit.MINUTES);。したがって、これは、すべてのスレッドが作業を完了するか、タイムアウトが発生するまで(いずれが先でも)待機する必要があると思います。私の仮定は正しいですか?または呼び出しshutdown()は必須ですか?
Amogh 2015

52

joinスレッドにできます。スレッドが完了するまで、結合はブロックされます。

for (Thread thread : threads) {
    thread.join();
}

joinスローすることに注意してくださいInterruptedException。その場合はどうするかを決める必要があります(たとえば、他のスレッドをキャンセルして、不要な作業が行われないようにします)。


1
これらのスレッドは、互いに並行して実行されますか?
James Webster、

5
@JamesWebster:パラレル。
RYN

4
@James Webster:このステートメントt.join();は、現在のスレッドがスレッドがt終了するまでブロックすることを意味します。スレッドには影響しませんt
Mark Byers、2011年

1
ありがとう。=]ユニでパラレリズムを学びましたが、それが私が学ぶのに苦労した唯一のものでした!ありがたいことに私は非常に今、それを使用する必要はありませんか私が行うとき、それはあまりにも複雑ではないのですか、何の共有リソースが存在しないとブロッキングが重要ではない
ジェームズ・ウェブスター

1
@ 4r1y4n提供されたコードが本当に並列であるかどうかは、コードで何をしようとしているかに依存し、結合されたスレッドを使用してコレクション全体に分散されたデータを集約することにさらに関連しています。スレッドを結合しています。つまり、データを「結合」する可能性があります。また、並列処理は必ずしも同時実行性を意味するわけではありません。それはCPUに依存します。スレッドが並列で実行されている可能性は十分にありますが、計算は基盤となるCPUによって決定される順序で行われます。

22

さまざまなソリューションをご覧ください。

  1. join()APIは、Javaの初期バージョンで導入されました。JDK 1.5リリース以降、この並行パッケージではいくつかの優れた代替手段が利用可能です。

  2. ExecutorService#invokeAll()

    指定されたタスクを実行し、すべてが完了したときにステータスと結果を保持するFutureのリストを返します。

    コード例については、この関連するSEの質問を参照してください。

    invokeAll()を使用してすべてのスレッドプールにタスクを実行させる方法は?

  3. CountDownLatch

    他のスレッドで実行されている一連の操作が完了するまで、1つまたは複数のスレッドを待機させる同期支援。

    A たCountDownLatchは、指定されたカウントで初期化されます。awaitメソッドは、countDown()メソッドの呼び出しによって現在のカウントがゼロに達するまでブロックします。その後、待機中のすべてのスレッドが解放され、後続のawaitの呼び出しはすぐに戻ります。これはワンショット現象です。カウントをリセットすることはできません。カウントをリセットするバージョンが必要な場合は、CyclicBarrierの使用を検討してください

    の使用法については、この質問を参照してください CountDownLatch

    自分のスレッドを生成するスレッドを待つ方法は?

  4. ForkJoinPoolまたは newWorkStealingPool()エグゼキュー

  5. 送信後に作成されたすべてのFutureオブジェクトを反復処理しますExecutorService


11

他の人からのThread.join()提案とは別に、java 5はexecutorフレームワークを導入しました。そこではThreadオブジェクトを操作しません。代わりに、CallableまたはRunnableオブジェクトをエグゼキュータに送信します。複数のタスクを実行してその結果を順不同で返すための特別なエグゼキューターがあります。それはExecutorCompletionService

ExecutorCompletionService executor;
for (..) {
    executor.submit(Executors.callable(yourRunnable));
}

次に、返すオブジェクトtake()がなくなるまでFuture<?>、つまり、すべてのオブジェクトが完了するまで繰り返し呼び出すことができます。


シナリオによっては、関連する可能性のあるもう1つの点はCyclicBarrierです。

一連のスレッドがすべてお互いが共通のバリアポイントに到達するのを待機できるようにする同期補助。CyclicBarriersは、時々互いに待機しなければならない固定サイズのスレッドのパーティが関与するプログラムで役立ちます。バリアは、待機中のスレッドが解放された後に再利用できるため、循環型と呼ばれます。


これは近いですが、私はまだいくつかの調整を行います。executor.submitを返しますFuture<?>。これらのフューチャーをリストに追加してから、get各フューチャーを呼び出すリストをループ処理します。
レイ

また、ExecutorsたとえばExecutors.newCachedThreadPool(または類似の)を使用してコンストラクタをインスタンス化できます
Ray

10

もう1つの可能性はCountDownLatchオブジェクトです。これは単純な状況で役立ちます。スレッドの数は事前にわかっているため、関連するカウントで初期化し、オブジェクトの参照を各スレッドに渡します。
タスクが完了すると、各スレッドはCountDownLatch.countDown()内部カウンターをデクリメントして呼び出します。メインスレッドは、他のすべてを開始した後、CountDownLatch.await()ブロッキング呼び出しを実行する必要があります。内部カウンタが0になるとすぐに解放されます。

このオブジェクトを使用すると、InterruptedExceptionもスローできることに注意してください。


8

あなたがやる

for (Thread t : new Thread[] { th1, th2, th3, th4, th5 })
    t.join()

このforループの後、すべてのスレッドがジョブを完了したことを確認できます。


6

他のスレッドが作業を完了するまで、スレッドメインを待機/ブロックします。

ASは@Ravindra babu、それが様々な方法で達成することができると述べたが、実施例で示します。

  • java.lang.Thread。join() 以降:1.0

    public static void joiningThreads() throws InterruptedException {
        Thread t1 = new Thread( new LatchTask(1, null), "T1" );
        Thread t2 = new Thread( new LatchTask(7, null), "T2" );
        Thread t3 = new Thread( new LatchTask(5, null), "T3" );
        Thread t4 = new Thread( new LatchTask(2, null), "T4" );
    
        // Start all the threads
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        // Wait till all threads completes
        t1.join();
        t2.join();
        t3.join();
        t4.join();
    }
  • java.util.concurrent.CountDownLatch 以降:1.5

    • .countDown() «ラッチグループのカウントを減らします。
    • .await() «awaitメソッドは、現在のカウントがゼロになるまでブロックします。

    作成したlatchGroupCount = 4場合countDown()は、4回呼び出してカウントを0にする必要がありますawait()。つまり、ブロックしているスレッドが解放されます。

    public static void latchThreads() throws InterruptedException {
        int latchGroupCount = 4;
        CountDownLatch latch = new CountDownLatch(latchGroupCount);
        Thread t1 = new Thread( new LatchTask(1, latch), "T1" );
        Thread t2 = new Thread( new LatchTask(7, latch), "T2" );
        Thread t3 = new Thread( new LatchTask(5, latch), "T3" );
        Thread t4 = new Thread( new LatchTask(2, latch), "T4" );
    
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    
        //latch.countDown();
    
        latch.await(); // block until latchGroupCount is 0.
    }

ThreadedクラスのサンプルコードLatchTask。アプローチをテストするにはjoiningThreads();latchThreads();メインメソッドから使用します。

class LatchTask extends Thread {
    CountDownLatch latch;
    int iterations = 10;
    public LatchTask(int iterations, CountDownLatch latch) {
        this.iterations = iterations;
        this.latch = latch;
    }

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " : Started Task...");

        for (int i = 0; i < iterations; i++) {
            System.out.println(threadName + " : " + i);
            MainThread_Wait_TillWorkerThreadsComplete.sleep(1);
        }
        System.out.println(threadName + " : Completed Task");
        // countDown() « Decrements the count of the latch group.
        if(latch != null)
            latch.countDown();
    }
}
  • CyclicBarriers一連のスレッドがお互いが共通のバリアポイントに到達するのをすべて待機できるようにする同期補助。CyclicBarriersは、時々お互いに待機する必要がある固定サイズのスレッドのパーティが関与するプログラムで役立ちます。バリアは、待機中のスレッドが解放された後に再利用できるため、循環型と呼ばれます。
    CyclicBarrier barrier = new CyclicBarrier(3);
    barrier.await();
    たとえば、このConcurrent_ParallelNotifyiesクラスを参照してください。

  • 実行フレームワーク:ExecutorServiceを使用してスレッドプールを作成し、Futureを使用して非同期タスクの進行状況を追跡できます。

    • submit(Runnable)submit(Callable)Future Objectを返します。future.get()関数を使用して、作業スレッドが作業を完了するまでメインスレッドをブロックできます。

    • invokeAll(...) -各Callableの実行結果を取得できるFutureオブジェクトのリストを返します。

エグゼキューターフレームワークでインターフェースRunnable、Callableを使用する見つけます


@も参照


4

スレッドオブジェクトをコレクション(リストやセットなど)に格納し、スレッドが開始されたらコレクションをループし、スレッドでjoin()を呼び出します。



2

OPの問題には関係ありませんが、1つのスレッドとの同期(より正確には、ランデブー)に関心がある場合は、Exchangerを使用できます。

私の場合、子スレッドが何かを行うまで、たとえば初期化が完了するまで、親スレッドを一時停止する必要がありました。たCountDownLatchはまたよく働きます。



1

これを試して、動作します。

  Thread[] threads = new Thread[10];

  List<Thread> allThreads = new ArrayList<Thread>();

  for(Thread thread : threads){

        if(null != thread){

              if(thread.isAlive()){

                    allThreads.add(thread);

              }

        }

  }

  while(!allThreads.isEmpty()){

        Iterator<Thread> ite = allThreads.iterator();

        while(ite.hasNext()){

              Thread thread = ite.next();

              if(!thread.isAlive()){

                   ite.remove();
              }

        }

   }

1

同様の問題があり、Java 8 parallelStreamを使用してしまいました。

requestList.parallelStream().forEach(req -> makeRequest(req));

とてもシンプルで読みやすいです。背後では、デフォルトのJVMのフォーク結合プールを使用しています。つまり、すべてのスレッドが完了するのを待ってから続行します。私の場合、それが私のアプリケーションで唯一のparallelStreamだったので、それはきちんとしたソリューションでした。複数のparallelStreamを同時に実行している場合は、以下のリンクをお読みください。

並列ストリームの詳細については、こちらをご覧ください


0

既存の答えは、join()それぞれのスレッドで可能であると述べました。

ただし、スレッドの配列/リストを取得するにはいくつかの方法があります。

  • スレッドを作成時にリストに追加します。
  • ThreadGroupスレッドの管理に使用します。

次のコードはこのThreadGruopアプローチを使用します。最初にグループを作成し、次に各スレッドを作成するときにコンストラクタでグループを指定すると、後でスレッド配列を取得できますThreadGroup.enumerate()


コード

SyncBlockLearn.java

import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * synchronized block - learn,
 *
 * @author eric
 * @date Apr 20, 2015 1:37:11 PM
 */
public class SyncBlockLearn {
    private static final int TD_COUNT = 5; // thread count
    private static final int ROUND_PER_THREAD = 100; // round for each thread,
    private static final long INC_DELAY = 10; // delay of each increase,

    // sync block test,
    @Test
    public void syncBlockTest() throws InterruptedException {
        Counter ct = new Counter();
        ThreadGroup tg = new ThreadGroup("runner");

        for (int i = 0; i < TD_COUNT; i++) {
            new Thread(tg, ct, "t-" + i).start();
        }

        Thread[] tArr = new Thread[TD_COUNT];
        tg.enumerate(tArr); // get threads,

        // wait all runner to finish,
        for (Thread t : tArr) {
            t.join();
        }

        System.out.printf("\nfinal count: %d\n", ct.getCount());
        Assert.assertEquals(ct.getCount(), TD_COUNT * ROUND_PER_THREAD);
    }

    static class Counter implements Runnable {
        private final Object lkOn = new Object(); // the object to lock on,
        private int count = 0;

        @Override
        public void run() {
            System.out.printf("[%s] begin\n", Thread.currentThread().getName());

            for (int i = 0; i < ROUND_PER_THREAD; i++) {
                synchronized (lkOn) {
                    System.out.printf("[%s] [%d] inc to: %d\n", Thread.currentThread().getName(), i, ++count);
                }
                try {
                    Thread.sleep(INC_DELAY); // wait a while,
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.printf("[%s] end\n", Thread.currentThread().getName());
        }

        public int getCount() {
            return count;
        }
    }
}

メインスレッドは、グループ内のすべてのスレッドが完了するのを待ちます。


0

いくつかのスレッドが完了するのを待つ小さなヘルパーメソッドを作成しました。

public static void waitForThreadsToFinish(Thread... threads) {
        try {
            for (Thread thread : threads) {
                thread.join();
            }
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

-1

これをメインスレッドで使用します:while(!executor.isTerminated()); executorサービスからすべてのスレッドを開始した後、このコード行を配置します。これは、エグゼキューターによって開始されたすべてのスレッドが終了した後でのみメインスレッドを開始します。必ずexecutor.shutdown();を呼び出してください。上記のループの前。


これはアクティブな待機であるため、CPUは常に空のループを実行します。非常に無駄です。
Adam Michalik 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.