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

问题

我有一个名为StartDownload()的方法的对象,它启动三个线程。

如何在每个线程执行完毕后收到通知?

有没有办法知道一个(或全部)线程是完成还是仍在执行?


#1 热门回答(203 赞)

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

  • 在主线程中使用Thread.join()以阻塞方式等待每个线程完成,或者
  • 以轮询方式检查Thread.isAlive() - 通常不鼓励 - 等待每个线程完成,或者
  • 非正统,对于每个有问题的线程,调用setUncaughtExceptionHandler来调用对象中的方法,并对每个Thread进行编程,以便在完成时抛出未被捕获的异常,或者
  • 使用java.util.concurrent中的锁或同步器或机制,或
  • 更正统,在主线程中创建一个监听器,然后对每个线程进行编程,告诉监听器它们已经完成。

如何实现Idea#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();
}

然后你的每个线程将扩展224241568而不是实现run()it将实现doRun()。因此,当他们完成时,他们将自动通知等待通知的任何人。

最后,在你的主类 - 启动所有线程(或至少等待通知的对象)的类中 - 将该类修改为2960608254并在创建每个线程后立即将其自身添加到侦听器列表:

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

然后,当每个Thread退出时,将使用刚刚完成(或崩溃)的Thread实例调用yournotifyOfThreadComplete方法。

注意,更好的是toimplements Runnable,而不是extends ThreadforNotifyingThread,在新代码中通常不鼓励扩展Thread。但我正在编写你的问题。如果你更改了NotifyingThreadclass以实现Runnable,那么你必须更改一些管理线程的代码,这非常简单。


#2 热门回答(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())

label 1-n-DownloadingThread进入候诊室

标签3- NUMBER_OF_DOWNLOADING_THREADS已进入候诊室。执行的主要线程释放它们以开始在或多或少的同时开始下载作业

label 4-主要执行线程进入候补室。这是要理解的代码中"最棘手"的部分。哪个线程第二次进入候诊室并不重要。重要的是,无论什么线程进入房间最后确保所有其他下载线程已完成其下载作业。

标签2- n-DownloadingThread已完成下载工作并进入候诊室。如果它是最后一个,即已经有NUMBER_OF_DOWNLOADING_THREADS个进入它,包括执行的主线程,主线程将仅在所有其他线程完成下载时继续执行。


#3 热门回答(7 赞)

你应该首选使用java.util.concurrent的解决方案。查找和阅读Josh Bloch和/或Brian Goetz的主题。

如果你没有使用java.util.concurrent.*并且负责直接使用线程,那么你应该使用join()来了解线程何时完成。这是一个超级简单的回调机制。首先扩展Runnable接口以进行回调:

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

然后创建一个将执行runnable的Executor,并在完成后回拨给你。

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接口的另一种显而易见的事情是处理任何异常的方法,因此可能在其中和执行程序中放入apublic void uncaughtException(Throwable e);line,安装Thread.UncaughtExceptionHandler以将你发送到该接口方法。

但所有这一切真的开始闻起来像java.util.concurrent.Callable。如果你的项目允许,你应该真正关注使用java.util.concurrent