如何知道其他线程是否已完成?


Answers:


227

您可以通过多种方式执行此操作:

  1. 在主线程中使用Thread.join()以阻塞方式等待每个线程完成,或者
  2. 以轮询方式(通常不鼓励使用检查Thread.isAlive(),直到每个线程完成为止,或者
  3. 非正统的,对于每个有问题的线程,调用setUncaughtExceptionHandler来调用对象中的方法,并对每个线程进行编程以在完成时引发未捕获的Exception,或者
  4. 使用java.util.concurrent中的锁或同步器或机制,或者
  5. 更传统的做法是,在主线程中创建一个侦听器,然后对每个线程进行编程以告知侦听器它们已完成。

如何实施想法5?好吧,一种方法是首先创建一个接口:

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将使用刚刚完成(或崩溃)的Thread实例调用您的方法。

需要注意的是更好的将implements Runnable,而不是extends Thread用于NotifyingThread为延长线为新的代码通常气馁。但是,我正在编码您的问题。如果您更改NotifyingThread要实现的类,Runnable则必须更改一些管理线程的代码,这非常简单。


嗨!我喜欢最后一个主意。我实现一个监听器来做到这一点?感谢
Ricardo Felgueiras 2009年

4
但是使用这种方法,在run()内部调用了notifiyListeners,因此它将被称为坚持线程,并且进一步的调用也将在该线程中完成,不是这样吗?
JordiPuigdellívol,2013年

1
@Eddie Jordi在问,是否有可能notify不是在run方法之后而是在方法之后调用方法。
TomaszDzięcielewski2013年

1
真正的问题是:你怎么关闭辅助线程的现在。我知道已经完成了,但是现在如何访问线程?
帕特里克

1
这个线程安全吗?似乎将在NotifyingThread中而不是在创建该侦听器本身的线程中调用notifyListeners(并因此是notifyOfThreadComplete)。
亚伦

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-第n个DownloadingThread进入等候室

标签3 -NUMBER_OF_DOWNLOADING_THREADS个进入等候室。执行的主线程释放他们开始或多或少同时开始下载工作

标签4-执行的主线程进入候诊室。这是代码中“最复杂”的部分。哪个线程第二次进入等候室无关紧要。重要的是,无论哪个线程最后进入房间,都必须确保所有其他下载线程都已完成其下载工作。

标签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接口中的另一种显而易见的事情是处理任何异常的方法,因此也许public void uncaughtException(Throwable e);在其中放置一行,然后在执行程序中安装Thread.UncaughtExceptionHandler以将您发送到该接口方法。

但是做的一切真的开始像臭味了java.util.concurrent.Callablejava.util.concurrent如果项目允许,您应该真正考虑使用。


我不清楚从该回调机制中获得什么,而与简单地调用runner.join()然后从中获取任何代码有关,因为您知道线程已完成。仅仅是将代码定义为可运行对象的属性,这样对于不同的可运行对象可以有不同的东西吗?
斯蒂芬

2
是的,这runner.join()是最直接的等待方式。我以为OP不想阻止其主调用线程,因为他们要求每次下载都要“通知”,该下载可以以任何顺序完成。这提供了一种异步通知的方法。
broc.seib

4

您要等待他们完成吗?如果是这样,请使用Join方法。

如果您只想检查它,则还有isAlive属性。


3
请注意,如果线程尚未开始执行(即使您自己的线程已经在其上调用start),则isAlive返回false。
Tom Hawtin-抢险

@ TomHawtin-tackline你确定吗?这将与Java文档相矛盾(“如果线程已经启动并且尚未死亡,则它是活动的” -docs.oracle.com/javase/6/docs/api/java/lang/…)。这也将在此矛盾的答案(stackoverflow.com/questions/17293304/...
斯蒂芬

@Stephen自从我写那封信以来已有很长时间了,但这似乎是真的。我以为它引起了其他人的问题,而这些问题在我九年前记忆犹新。实际可观察​​的内容将取决于实现。您告诉Threadto start,该线程执行哪个操作,但是调用立即返回。isAlive应该是一个简单的标志测试,但是当我用Google搜索它时,方法是native
汤姆·哈汀

4

您可以使用getState()来查询线程实例,该实例返回具有以下值之一的Thread.State枚举实例:

*  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个子线程完成后再继续执行。


等待这三个孩子退出可能不适合使用范例。如果这是一个下载管理器,他们可能希望开始15次下载,然后只需从状态栏中删除状态或在下载完成时提醒用户,在这种情况下,回调会更好地工作。
digitaljoel

3

您也可以使用该Executors对象创建 ExecutorService线程池。然后使用该invokeAll方法来运行每个线程并检索Future。这将阻塞,直到所有执行完毕。您的另一个选择是使用该池执行每个执行,然后调用awaitTermination阻塞,直到该池执行完毕。shutdown完成添加任务后,只需确保调用()。


2

在过去的6年中,多线程方面发生了许多变化。

除了使用join()和锁定API外,您还可以使用

1. ExecutorService invokeAll() API

执行给定的任务,并在所有任务完成时返回保存其状态和结果的期货列表。

2. CountDownLatch

一种同步帮助,它允许一个或多个线程等待,直到在其他线程中执行的一组操作完成为止。

CountDownLatch用给定的计数初始化A。由于该countDown()方法的调用,直到当前计数达到零为止,await方法将阻塞,此后所有释放的线程将被释放,任何随后的await调用将立即返回。这是一种一次性现象-无法重置计数。如果需要用于重置计数的版本,请考虑使用CyclicBarrier。

3. ForkJoinPoolnewWorkStealingPool()执行人是另一种方式

4,遍历Future提交后的所有任务,ExecutorService并通过阻止对象调用get()来检查状态Future

看看相关的SE问题:

如何等待产生自己线程的线程?

执行程序:如果递归创建任务,如何同步等待所有任务完成?


2

我建议查看Thread 类的javadoc 。

您有多种线程处理机制。

  • 您的主线程可以join()依次执行三个线程,然后在所有三个线程都完成之后再继续进行。

  • 间隔轮询生成的线程的线程状态。

  • 将所有生成的线程放在单独的位置,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”在主线程中运行,因此每秒可以检查另一个线程。当另一个线程结束时,这可以开始我们接下来要做的任何事情。

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()一旦执行程序完全终止,可以重写方法来执行需要执行的任何特殊处理。

这正是我们所需要的。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 
    }

内部方法informUiThatWeAreDone();可以完成所有线程后所需执行的任何操作,例如,更新UI。

注意:不要忘记使用synchronized方法,因为您并行进行工作,如果决定synchronized从另一个方法调用方法,请务必小心synchronized方法!这通常会导致死锁

希望这可以帮助!



0

查看Thread类的Java文档。您可以检查线程的状态。如果将三个线程放入成员变量中,则所有三个线程都可以读取彼此的状态。

但是,您必须谨慎一些,因为这可能导致线程之间的竞争状况。只是尝试避免基于其他线程状态的复杂逻辑。绝对避免多个线程写入相同的变量。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.