在Java中等待多个线程完成

45

在程序执行期间,我会启动许多线程。线程的数量取决于用户定义的设置,但它们都会使用不同的变量执行相同的方法。

在某些情况下,需要在执行过程中进行清理,其中一部分是停止所有线程。但我不希望它们立即停止,而是设置一个变量供它们检查以终止它们。问题是,直到线程停止可能需要长达1/2秒的时间。但是,在清理可以继续之前,我需要确保所有线程都已停止。清理是由另一个线程执行的,因此从技术上讲,我需要此线程等待其他线程完成。

我想到了几种方法来实现这一点,但它们似乎都过于复杂。我希望有一种方法可以等待一组线程完成。是否存在类似的内容呢?


可能是重复的问题:如何等待一组线程完成? - Ravindra babu
5个回答

77

只需逐个连接它们:

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

(您需要处理 InterruptedException,并且您可能希望在出现问题时提供超时,但这就是基本思路...)


16
@Jon Skeet,我有一个疑问。代码行thread.join();会让当前进程等待直到线程thread运行完毕,对吗?那么这里会发生什么情况呢,主进程将执行第二行并等待thread完成其工作,然后它将转到循环中的下一个thread,所以实际上主进程会等待第一个子线程正在运行,然后它将为下一个线程执行thread.join(),我理解对吗? - Patriks
1
@Pratik:它将阻塞当前的线程(而不是进程),直到thread完成(而不是启动)。期望的是,在我的回答代码执行之前,所有线程都已经在运行...它只是等待每个线程完成 - Jon Skeet
5
@Jon Skeet,请帮我澄清一下,我理解如果主线程调用 thread.join(),那么主线程将会被暂停,直到线程 thread 执行完成后主线程才会恢复。我理解得对吗?现在,假设有3个线程存储在变量 threads 中,那么 for 循环将会执行3次,第1次循环时,主线程将执行 .join() 方法,然后被系统暂停,当第一个线程执行完成后,主线程才能继续循环并处理下一个(第2个)线程元素。我理解的对吗? - Patriks
9
还有一点需要注意的是,当主线程移动到加入第二个线程时,它可能已经完成了。在这种情况下,加入操作将立即返回。因此,主线程将继续移动到加入第三个线程。 - AppleGrew
1
@ThomasAhle:是的,有时候立即退出是正确的做法;其他情况下,您可能需要以不同的方式处理。 - Jon Skeet
显示剩余4条评论

15

如果您使用的是Java 1.5或更高版本,则可以尝试使用CyclicBarrier。您可以将清理操作作为其构造函数参数传递,并在需要清理时在所有线程上调用barrier.await()


谢谢,这似乎可以满足我的需求,但当我看到这个答案时,我已经写了Jon的答案。 - A Jackson
3
一种替代方案是,如果您只需要执行一次,可以使用CountdownLatch。 - Dan Dyer

15

你是否看过 java.util.concurrent 中的 Executor 类? 你可以通过 ExecutorService 运行线程。 它会提供一个单一的对象,可用于取消线程或等待它们完成。


1
出乎意料的是,这确实是最好的方法。它允许您使用多个线程,但保持有限数量的线程,可能基于设备上的核心数。 - Joshua Pinter

8

定义自己的实用方法(或方法):

public static waitFor(Collection<? extends Thread) c) throws InterruptedException {
    for(Thread t : c) t.join();
}

或者你可能有一个数组

public static waitFor(Thread[] ts) throws InterruptedException {
    waitFor(Arrays.asList(ts));
}

或者您可以考虑使用 java.util.concurrent 库中的 CyclicBarrier 来实现多个线程之间的任意 汇合点


3
如果您控制线程的创建(提交给ExecutorService),那么似乎可以使用ExecutorCompletionService,有关答案,请参见ExecutorCompletionService?为什么需要它,如果我们有invokeAll?
如果您不控制线程创建,则可以通过启发自ruby ThreadWait类的方法,允许您“逐个加入”线程(并知道哪个线程先完成等),从而达到相同的效果。基本上通过新建“观察线程”,当其他线程终止时发出警报,您可以知道许多线程中的“下一个”线程何时终止。
您可以像以下方式使用它:
JoinThreads join = new JoinThreads(threads);
for(int i = 0; i < threads.size(); i++) {
  Thread justJoined = join.joinNextThread();
  System.out.println("Done with a thread, just joined=" + justJoined);
}

并且源代码:

public static class JoinThreads {
  java.util.concurrent.LinkedBlockingQueue<Thread> doneThreads = 
      new LinkedBlockingQueue<Thread>();

  public JoinThreads(List<Thread> threads) {
    for(Thread t : threads) {
      final Thread joinThis = t;
      new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            joinThis.join();
            doneThreads.add(joinThis);
          }
          catch (InterruptedException e) {
            // "should" never get here, since we control this thread and don't call interrupt on it
          }
        }
      }).start();
    }

  }

  Thread joinNextThread() throws InterruptedException {
    return doneThreads.take();
  }
}

这个好处在于它可以与通用的Java线程一起工作,无需修改,任何线程都可以加入。但缺点是需要额外创建一些线程。此外,如果您不调用joinNextThread()完整数量的次数,则该特定实现会“留下线程”,并且没有“关闭”方法等等。如果您想要创建更加完善的版本,请在此处发表评论。您还可以使用“Futures”而不是线程对象等相同类型的模式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接