他のスレッドが終了したかどうかを知る方法は?


125

StartDownload()3つのスレッドを開始するという名前のメソッドを持つオブジェクトがあります。

各スレッドの実行が終了したときに通知を受け取るにはどうすればよいですか?

スレッドの1つ(またはすべて)が終了しているか、まだ実行中であるかを知る方法はありますか?


回答:


227

これを行う方法はいくつかあります。

  1. メインスレッドでThread.join()を使用して、各スレッドが完了するのをブロックする方法で待機する、または
  2. ポーリング方式でThread.isAlive()をチェックします(通常は非推奨)。各スレッドが完了するまで待機するか、
  3. 正統ではない、問題のスレッドごとに、setUncaughtExceptionHandlerを呼び出してオブジェクトのメソッドを呼び出し、完了時にキャッチされない例外をスローするように各スレッドをプログラムする、または
  4. java.util.concurrentのロック、シンクロナイザー、またはメカニズムを使用する、または
  5. より正統的な方法で、メインスレッドにリスナーを作成し、各スレッドをプログラムして、完了したことをリスナーに通知します。

アイデア#5の実装方法は?1つの方法は、最初にインターフェースを作成することです。

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

次に、次のクラスを作成します。

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

その後、各スレッドが拡張NotifyingThreadされ、実装run()する代わりに実装されdoRun()ます。したがって、完了すると、通知を待っている人に自動的に通知されます。

最後に、メインクラス(すべてのスレッド(または少なくとも通知を待機しているオブジェクト)を開始するクラス)でクラスを変更し、implement ThreadCompleteListener各スレッドを作成した直後に自分自身をリスナーのリストに追加します。

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

次に、各スレッドが終了すると、notifyOfThreadComplete完了した(またはクラッシュした)スレッドインスタンスを使用してメソッドが呼び出されます。

より良いがにされることに注意してくださいimplements Runnableではなく、extends ThreadためにNotifyingThread拡張スレッドは通常、新しいコードでは推奨されて。しかし、私はあなたの質問にコーディングしています。NotifyingThreadクラスを変更して実装Runnableする場合は、スレッドを管理するコードの一部を変更する必要があります。これは非常に簡単です。


こんにちは!!私は最後のアイデアが好きです。それを行うためにリスナーを実装しますか?ありがとう
Ricardo Felgueiras 2009年

4
しかし、このアプローチを使用すると、notifiyListenersがrun()内で呼び出されるため、スレッドが呼び出されて呼び出され、以降の呼び出しもそこで行われます。そうではありませんか?
JordiPuigdellívol2013年

1
@Eddie Jordiはnotifyrunメソッドではなくメソッドの後にメソッドを呼び出すことができるかどうか尋ねていました。
TomaszDzięcielewski2013

1
問題は、実際には次のとおりです。どうすれば今、セカンダリスレッドをオフにできますか。完了したことはわかっていますが、メインスレッドにアクセスするにはどうすればよいですか。
Patrick

1
このスレッドは安全ですか?notifyListeners(したがってnotifyOfThreadComplete)は、リスナー自体を作成したスレッド内ではなく、NotifyingThread内で呼び出されるようです。
アーロン

13

CyclicBarrierを使用したソリューション

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

label0-実行中のスレッド数+実行のメインスレッド(startDownload()が実行されている)の1に等しい数のパーティで循環バリアが作成されます

ラベル1 -n番目のDownloadingThreadが待合室に入る

ラベル3 -NUMBER_OF_DOWNLOADING_THREADSが待合室に入りました。実行のメインスレッドはそれらを解放して、ほぼ同時にダウンロードジョブの実行を開始します

ラベル4-実行のメインスレッドが待合室に入ります。これは、理解する必要があるコードの「トリッキー」な部分です。どのスレッドが2回目に待合室に入るかは関係ありません。部屋に最後に入るスレッドがどれであっても、他のすべてのダウンロードスレッドがダウンロードジョブを確実に終了することが重要です。

ラベル2 -n番目のDownloadingThreadはダウンロードジョブを完了し、待合室に入ります。それが最後のスレッドである場合、つまり実行のメインスレッドを含め、すでにNUMBER_OF_DOWNLOADING_THREADSが入力されている場合、メインスレッドは、他のすべてのスレッドのダウンロードが終了したときにのみ実行を継続します。


9

あなたは本当にを使用するソリューションを好むはずjava.util.concurrentです。トピックに関するJosh BlochやBrian Goetzを見つけて読んでください。

java.util.concurrent.*スレッドを使用しておらず、直接スレッドを使用する責任join()がある場合は、スレッドがいつ完了したかを知るために使用する必要があります。これが超シンプルなコールバックメカニズムです。まず、Runnableインターフェースを拡張してコールバックを作成します。

public interface CallbackRunnable extends Runnable {
    public void callback();
}

次に、ランナブルを実行するエグゼキューターを作成し、完了したらコールバックします。

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

CallbackRunnableインターフェイスに追加するもう1つの種類の明らかなことは、例外を処理する手段であるため、おそらくpublic void uncaughtException(Throwable e);そこに行を入れ、エグゼキューターにThread.UncaughtExceptionHandlerをインストールして、そのインターフェイスメソッドに送信します。

しかし、すべてのことを行うと、本当に匂いがし始めjava.util.concurrent.Callableます。java.util.concurrentプロジェクトで許可されている場合は、実際に使用する必要があります。


runner.join()スレッドが終了したことがわかっているので、このコールバックメカニズムから何が得られるのか、単純に呼び出してから、その後に必要なコードを取得することについては、少し不明確です。そのコードをランナブルのプロパティとして定義するだけで、ランナブルごとに異なるものを持つことができますか?
スティーブン

2
はい、runner.join()待つ最も直接的な方法です。OPはメインの呼び出しスレッドをブロックしたくないと思っていました。ダウンロードごとに「通知」を求められたためです。これにより、非同期で通知を受ける1つの方法が提供されました。
broc.seib 2018年

4

それらが完了するのを待ちますか?その場合は、Joinメソッドを使用します。

確認したいだけの場合はisAliveプロパティもあります。


3
スレッドがまだ実行を開始していない場合は、isAliveはfalseを返すことに注意してください(独自のスレッドがすでにstartを呼び出している場合でも)。
トムホーティン-タックライン2009年

@ TomHawtin-tacklineあなたはそれについてかなり確信していますか?これは、Javaのドキュメントと矛盾します(「スレッドが開始されていて、まだ終了していない場合、スレッドは有効です」-docs.oracle.com/javase/6/docs/api/java/lang/…)。また、ここでの回答(stackoverflow.com/questions/17293304/…)と矛盾します
スティーブン

@Stephen私が書いてから久しぶりですが、本当のようです。それが9年前に私の記憶に新鮮な他の人々の問題を引き起こしたと思います。何が観察可能かは、実装によって異なります。スレッドが行うThreadtoを伝えstartますが、呼び出しはすぐに戻ります。isAlive簡単なフラグテストである必要がありますが、Googleでググるとその方法はでしたnative
トムホーティン-タックライン2018年

4

次のいずれかの値を持つThread.State列挙のインスタンスを返すgetState()でスレッドインスタンスに問い合わせることができます。

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

ただし、3つの子が完了するのを待つマスタースレッドを使用する方が良い設計になると思います。マスターは、他の3つが完了すると実行を継続します。


3人の子供が終了するのを待つことは、使用法のパラダイムに適合しない場合があります。これがダウンロードマネージャーの場合、15個のダウンロードを開始して、ステータスバーからステータスを削除するか、ダウンロードが完了したときにユーザーに警告することができます。
digitaljoel 2009年

3

Executorsオブジェクトを使用して、ExecutorServiceスレッドプールを作成する こともできます。次に、このinvokeAllメソッドを使用して各スレッドを実行し、Futureを取得します。これは、すべての実行が完了するまでブロックされます。他のオプションは、プールを使用してそれぞれを実行してawaitTerminationから、プールの実行が完了するまでブロックを呼び出すことです。shutdownタスクの追加が完了したら、必ず()を呼び出してください。


2

過去6年間にマルチスレッドの面で多くのことが変更されました。

join()API を使用してロックする代わりに、

1. ExecutorService invokeAll() API

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

2. CountDownLatch

1つ以上のスレッドが、他のスレッドで実行されている一連の操作が完了するまで待機することを可能にする同期支援。

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

3. ForkJoinPoolnewWorkStealingPool()エグゼキュータは、他の方法です。

4.送信からすべてのFutureタスクを繰り返し、オブジェクトのExecutorServiceブロック呼び出しget()でステータスを確認しFutureます

関連するSEの質問をご覧ください。

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

実行者:タスクが再帰的に作成された場合、すべてのタスクが完了するまで同期的に待機する方法は?


2

スレッド クラスのjavadocを確認することをお勧めします。

スレッド操作には複数のメカニズムがあります。

  • メインスレッドjoin()は3つのスレッドを順番に実行でき、3つすべてが完了するまで続行されません。

  • 生成されたスレッドのスレッド状態を一定間隔でポーリングします。

  • 生成されたスレッドをすべて別のスレッドに入れ、ThreadGroupをポーリングしactiveCount()て、ThreadGroupそれが0になるまで待ちます。

  • スレッド間通信用のカスタムコールバックまたはリスナータイプのインターフェイスをセットアップします。

他にもまだ足りない方法がたくさんあると思います。


1

シンプルで短く、理解しやすく、完璧に機能するソリューションを次に示します。別のスレッドが終了したときに画面に描画する必要がありました。メインスレッドが画面を制御しているため、できませんでした。そう:

(1)グローバル変数を作成しましたboolean end1 = false;。スレッドは終了時にtrueに設定します。これは、「postDelayed」ループによってメインスレッドでピックアップされ、そこで応答されます。

(2)私のスレッドには以下が含まれます:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3)幸い、「postDelayed」はメインスレッドで実行されるため、1秒に1回、他のスレッドをチェックします。他のスレッドが終了すると、次に何をしたいかを開始できます。

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4)最後に、次のコードを呼び出して、コードのどこかですべてを実行します。

void startThread()
{
   myThread();
   checkThread();
}

1

ThreadPoolExecutorクラスを使うのが一番簡単だと思います。

  1. キューがあり、並行して動作するスレッドの数を設定できます。
  2. それは素晴らしいコールバックメソッドを持っています:

フックメソッド

このクラスは、各タスクの実行前と実行後に呼び出されるbeforeExecute(java.lang.Thread, java.lang.Runnable)、保護されたオーバーライド可能なafterExecute(java.lang.Runnable, java.lang.Throwable)メソッドとメソッドを提供します。これらは、実行環境を操作するために使用できます。たとえば、ThreadLocalsの再初期化、統計の収集、ログエントリの追加などです。さらに、メソッドterminated()をオーバーライドして、Executorが完全に終了した後に実行する必要がある特別な処理を実行できます。

これがまさに私たちが必要としていることです。私たちは、上書きされます afterExecute()各スレッドが実行された後にコールバックを取得すると無効になりますterminated()すべてのスレッドが実行されたときに知っています。

だからここにあなたがすべきことです

  1. エグゼキューターを作成します。

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. スレッドを開始します。

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

InsideメソッドinformUiThatWeAreDone();は、たとえばUIの更新など、すべてのスレッドが完了したときに必要なことをすべて実行します。

注:synchronized並行して作業を行うため、メソッドの使用を忘れないでください。synchronized別のsynchronizedメソッドからメソッドを呼び出す場合は、十分に注意してください。これはしばしばデッドロックにつながります

お役に立てれば!



0

Threadクラスについては、Javaのドキュメントをご覧ください。スレッドの状態を確認できます。3つのスレッドをメンバー変数に入れると、3つのスレッドすべてが互いの状態を読み取ることができます。

ただし、スレッド間で競合状態が発生する可能性があるため、少し注意する必要があります。他のスレッドの状態に基づく複雑なロジックを回避するようにしてください。複数のスレッドが同じ変数に書き込むことは絶対に避けてください。

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