如何等待多个线程完成?

118

有什么简单的方法可以等待所有线程处理完成吗?例如,假设我有以下代码:

public class DoSomethingInAThread implements Runnable{

    public static void main(String[] args) {
        for (int n=0; n<1000; n++) {
            Thread t = new Thread(new DoSomethingInAThread());
            t.start();
        }
        // wait for all threads' run() methods to complete before continuing
    }

    public void run() {
        // do something here
    }


}

我该如何修改这个代码,使得main()方法在注释处暂停,直到所有线程的run()方法执行完毕?谢谢!

14个回答

164

你将所有线程放入一个数组中,启动它们,然后进行循环

for(i = 0; i < threads.length; i++)
  threads[i].join();

每个join操作将会阻塞当前线程直到对应的线程完成。不同的线程可能以不同的顺序完成,但这并不是一个问题:当循环结束时,所有的线程都已经完成。


1
@Mykola:使用线程组的确切优势是什么?仅仅因为有API并不意味着你必须使用它... - Martin v. Löwis
2
请参见:“线程组表示一组线程。”对于这种用例,这是语义上正确的!并且:“线程可以访问有关其自己线程组的信息” - Martin K.
5
《Effective Java》这本书建议避免使用线程组(第73条)。 - Bastien Léonard
2
Effective Java中提到的错误应该已经在Java 6中修复了。如果新版本的Java不是限制,最好使用Futures来解决线程问题。Martin v. Löwis:你说得对。这与该问题无关,但从一个对象(如ExecutorService)获取有关运行线程的更多信息很好。我认为使用给定的功能来解决问题很好;也许将来需要更多的灵活性(线程信息)。同时,提到旧版JDK中的旧有错误类也是正确的。 - Martin K.
6
ThreadGroup没有实现组级别的join,所以人们为什么要推ThreadGroup有点令人困惑。人们真的在使用自旋锁并查询组的activeCount吗?如果与只是调用所有线程的join相比,很难说服我这样做在任何方面都更好。 - carej
显示剩余4条评论

44
一种方法是创建一个 List,将每个 Thread 创建并启动后加入到列表中。一旦所有线程都启动,再通过循环遍历列表并对每个线程调用 join() 方法。无论线程以何种顺序执行完毕,你只需要知道在第二次循环执行完成时,每个线程都已经完成。
更好的方法是使用 ExecutorService 及其相关方法:
List<Callable> callables = ... // assemble list of Callables here
                               // Like Runnable but can return a value
ExecutorService execSvc = Executors.newCachedThreadPool();
List<Future<?>> results = execSvc.invokeAll(callables);
// Note: You may not care about the return values, in which case don't
//       bother saving them

使用ExecutorService(以及Java 5的并发工具中的所有新功能)非常灵活,上面的示例仅仅是皮毛。


ThreadGroup是正确的选择!使用可变列表会遇到麻烦(同步)。 - Martin K.
3
什么?你怎么会惹上麻烦呢?这个列表只能被启动线程修改(只读),只要在迭代列表时不对其进行修改,就没问题了。 - Adam Batkin
这取决于你如何使用它。如果你将在线程中使用调用类,那么你会遇到问题。 - Martin K.

29
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DoSomethingInAThread implements Runnable
{
   public static void main(String[] args) throws ExecutionException, InterruptedException
   {
      //limit the number of actual threads
      int poolSize = 10;
      ExecutorService service = Executors.newFixedThreadPool(poolSize);
      List<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

      for (int n = 0; n < 1000; n++)
      {
         Future f = service.submit(new DoSomethingInAThread());
         futures.add(f);
      }

      // wait for all tasks to complete before continuing
      for (Future<Runnable> f : futures)
      {
         f.get();
      }

      //shut down the executor service so that this thread can exit
      service.shutdownNow();
   }

   public void run()
   {
      // do something here
   }
}

运行得非常好...我有两组线程,由于多个cookie的问题,它们不应同时运行。我使用了您的示例来一次只运行一组线程..感谢分享您的知识... - arn-arn
@Dantalian - 在您的Runnable类中(可能在run方法中),您需要捕获任何发生的异常并将其存储在本地(或存储错误消息/条件)。在示例中,f.get()返回您提交给ExecutorService的对象。您的对象可以有一个检索任何异常/错误条件的方法。根据您如何修改提供的示例,您可能需要将f.get()返回的对象转换为您期望的类型。 - jt.

14

你可以使用CountDownLatch代替旧的API join()。我已经修改了你的代码以满足你的要求。

import java.util.concurrent.*;
class DoSomethingInAThread implements Runnable{
    CountDownLatch latch;
    public DoSomethingInAThread(CountDownLatch latch){
        this.latch = latch;
    } 
    public void run() {
        try{
            System.out.println("Do some thing");
            latch.countDown();
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

public class CountDownLatchDemo {
    public static void main(String[] args) {
        try{
            CountDownLatch latch = new CountDownLatch(1000);
            for (int n=0; n<1000; n++) {
                Thread t = new Thread(new DoSomethingInAThread(latch));
                t.start();
            }
            latch.await();
            System.out.println("In Main thread after completion of 1000 threads");
        }catch(Exception err){
            err.printStackTrace();
        }
    }
}

解释:

  1. CountDownLatch has been initialized with given count 1000 as per your requirement.

  2. Each worker thread DoSomethingInAThread will decrement the CountDownLatch, which has been passed in constructor.

  3. Main thread CountDownLatchDemo await() till the count has become zero. Once the count has become zero, you will get below line in output.

    In Main thread after completion of 1000 threads
    

来自 Oracle 文档页面的更多信息

public void await()
           throws InterruptedException

使当前线程等待,直到闩锁计数减为零,除非线程被中断。请参考相关SE问题以获取其他选项:在Java中等待所有线程完成工作

8
避免使用Thread类,而是使用java.util.concurrent提供的更高级的抽象。ExecutorService类提供了方法invokeAll,看起来正是你想要的。

6

考虑使用java.util.concurrent.CountDownLatch。在javadocs中有示例。


一个线程门闩,门闩锁定使用倒计时。在您的线程的run()方法中明确声明等待CountDownLatch达到0的倒计时。您可以在多个线程中使用相同的CountDownLatch同时释放它们。我不知道这是否是您需要的,只是想提一下,因为在多线程环境中工作时非常有用。 - Pablo Cavalieri
也许你应该把那个解释放在你的回答正文里? - Russia Must Remove Putin
Javadoc中的示例非常详细,这就是为什么我没有添加任何内容的原因。https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html。在第一个示例中,所有Worker线程都会同时释放,因为它们等待CountdownLatch startSignal达到零,这发生在startSignal.countDown()中。然后,主线程使用指令doneSignal.await()等待所有工作完成。doneSignal在每个工作者中减少其值。 - Pablo Cavalieri

6
根据您的需求,您可能还想查看java.util.concurrent包中的CountDownLatch和CyclicBarrier类。如果您希望线程相互等待,或者希望更精细地控制线程的执行方式(例如,在其内部执行等待另一个线程设置某些状态),则它们非常有用。您还可以使用CountDownLatch来发出信号,让所有线程同时启动,而不是在迭代循环中逐个启动它们。标准API文档中有一个示例,以及使用另一个CountDownLatch等待所有线程完成执行的示例。

5

正如Martin K所建议的那样,java.util.concurrent.CountDownLatch似乎是更好的解决方案。这里提供一个例子。

     public class CountDownLatchDemo
{

    public static void main (String[] args)
    {
        int noOfThreads = 5;
        // Declare the count down latch based on the number of threads you need
        // to wait on
        final CountDownLatch executionCompleted = new CountDownLatch(noOfThreads);
        for (int i = 0; i < noOfThreads; i++)
        {
            new Thread()
            {

                @Override
                public void run ()
                {

                    System.out.println("I am executed by :" + Thread.currentThread().getName());
                    try
                    {
                        // Dummy sleep
                        Thread.sleep(3000);
                        // One thread has completed its job
                        executionCompleted.countDown();
                    }
                    catch (InterruptedException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

            }.start();
        }

        try
        {
            // Wait till the count down latch opens.In the given case till five
            // times countDown method is invoked
            executionCompleted.await();
            System.out.println("All over");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

}

3

嗨,由于某些原因,它对我没有起作用。这是我的问题:http://stackoverflow.com/users/5144855/ruchir-baronia - Ruchir Baronia

1
在第一个for循环中创建线程对象。
for (int i = 0; i < threads.length; i++) {
     threads[i] = new Thread(new Runnable() {
         public void run() {
             // some code to run in parallel
         }
     });
     threads[i].start();
 }

然后,这里的每个人都在说什么。

for(i = 0; i < threads.length; i++)
  threads[i].join();

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