如何等待多个线程完成?


109

有什么方法可以简单地等待所有线程进程完成?例如,假设我有:

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()方法退出?谢谢!

Answers:


163

您将所有线程放入一个数组,全部启动,然后进行循环

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

每个连接将阻塞,直到相应的线程完成为止。线程的完成顺序可能不同于您加入线程的顺序,但这不是问题:退出循环时,所有线程均已完成。


1
@Mykola:使用线程组的好处到底是什么?仅仅因为那里有API并不意味着您必须使用它……
Martin v。Löwis09年

2
请参阅:“线程组代表一组线程。” 对于这个用例,这在语义上是正确的!并且:“允许线程访问有关其自己的线程组的信息”
Martin K.

4
《有效的Java》一书建议避免使用线程组(项目73)。
巴斯蒂安·莱纳德(BastienLéonard),2009年

2
有效Java中提到的错误应该已经在Java 6中修复。如果不是较新的Java版本,最好使用Future解决线程问题。Martin v。Löwis:你是对的。它与该问题无关,但是很高兴从一个对象(例如ExecutorService)中获取有关正在运行的线程的更多信息。我认为使用给定的功能解决问题很好。也许将来您将需要更多的灵活性(线程信息)。提及旧JDK中的旧越野车类也是正确的。
Martin K.

5
ThreadGroup并未实现组级别的联接,因此为什么人们推动ThreadGroup有点莫名其妙。人们真的在使用自旋锁并查询组的activeCount吗?您很难说服我,与仅在所有线程上调用join相比,这样做有任何好处。

41

一种方法是做一个ListThreadS,创建和启动每个线程,而将其添加到列表中。一切启动后,循环遍历该列表并调用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
什么?你怎么会惹麻烦?它仅是正在执行启动的线程的可变变量(只能读取),因此只要迭代过程中不修改列表就可以了。
亚当·巴特金

这取决于您如何使用它。如果您将在线程中使用调用类,则会遇到问题。
Martin K.

27
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。

12

join()可以使用CountDownLatch代替,而这是一个旧的API 。我已按照以下方式修改了您的代码,以满足您的要求。

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 已根据您的要求使用给定的计数1000进行了初始化。

  2. 每个工作线程DoSomethingInAThread 都会减少CountDownLatch,该值已在构造函数中传递。

  3. 主线程,CountDownLatchDemo await()直到计数变为零为止。一旦计数变为零,您将在输出下一行。

    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.CountDownLatchjavadocs中的示例


是用于线程的闩锁,闩锁具有倒数功能。在线程的run()方法中,显式声明要等待CountDownLatch达到其倒数至0。可以在多个线程中使用同一CountDownLatch来同时释放它们。我不知道它是否是您所需要的,只是想提一下它,因为它在多线程环境中工作时很有用。
Pablo Cavalieri 2014年

也许您应该将该解释放在您的答案正文中?
亚伦·霍尔

Javadoc中的示例非常具有描述性,这就是为什么我没有添加任何示例的原因。docs.oracle.com/javase/7/docs/api/java/util/concurrent/…。在第一个示例中,所有Workers线程同时被释放,因为它们等待CountdownLatch startSignal达到零,这在startSignal.countDown()中发生。然后,主线程使用指令doneSignal.await()等待直到所有工作完成。doneSignal会降低其在每个工作人员中的价值。
Pablo Cavalieri 2014年

6

正如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();
        }
    }

}

4

根据您的需求,您可能还需要检出java.util.concurrent包中的CountDownLatch和CyclicBarrier类。如果您希望线程彼此等待,或者想要对线程的执行方式进行更细粒度的控制(例如,在其内部执行中等待另一个线程设置某种状态),它们可能会很有用。您还可以使用CountDownLatch来指示所有线程同时启动,而不是在循环遍历时一个接一个地启动它们。标准API文档提供了一个示例,并使用另一个CountDownLatch等待所有线程完成其执行。



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();

0

不知道您到底打算怎么做。如果您打算在一个循环中轮询activeCount,那就不好了,因为它很忙(即使您在两次轮询之间都睡着了-您也会在业务和响应能力之间进行权衡)。
Martin v。Löwis09年

@Martin诉Löwis:“加入将只等待一个线程。一个更好的解决方案可能是java.util.concurrent.CountDownLatch。只需将计数设置为工作线程数即可初始化闩锁。每个工作线程都应调用countDown()就在它退出之前,主线程只是调用await(),它将一直阻塞直到计数器达到零为止。join()的问题还在于,您无法开始动态添加更多线程,该列表将会爆炸。并发修改。” 您的解决方案适用于问题,但不适用于一般用途。
Martin K.

0

作为CountDownLatch的替代方法,您还可以使用CyclicBarrier例如

public class ThreadWaitEx {
    static CyclicBarrier barrier = new CyclicBarrier(100, new Runnable(){
        public void run(){
            System.out.println("clean up job after all tasks are done.");
        }
    });
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new MyCallable(barrier));
            t.start();
        }       
    }

}    

class MyCallable implements Runnable{
    private CyclicBarrier b = null;
    public MyCallable(CyclicBarrier b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
            //do something
            System.out.println(Thread.currentThread().getName()+" is waiting for barrier after completing his job.");
            b.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }       
}

在这种情况下,要使用CyclicBarrier,barrier.await()应该是最后一条语句,即线程完成其工作时。CyclicBarrier可以通过其reset()方法再次使用。引用javadocs:

CyclicBarrier支持可选的Runnable命令,该命令在障碍中的最后一个线程到达之后但在释放任何线程之前,每个障碍点运行一次。此屏障操作对于在任何一方继续之前更新共享状态很有用。


我认为这不是CyclicBarrier的好例子。为什么要使用Thread.sleep()调用?
Guenther

@Guenther-是的,我更改了代码以适应需求。
shailendra1118

CyclicBarrier不能替代CountDownLatch。当线程必须反复递减计数时,您应该创建一个CyclicBarrier,否则默认为CountDownLatch(除非另外需要执行的抽象,这时您应该查看更高级别的服务)。
Elysiumplain

0

join()不是对我很有帮助。在Kotlin中查看此示例:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
        }
    })

结果:

Thread-5|a=5
Thread-1|a=1
Thread-3|a=3
Thread-2|a=2
Thread-4|a=4
Thread-2|2*1=2
Thread-3|3*1=3
Thread-1|1*1=1
Thread-5|5*1=5
Thread-4|4*1=4
Thread-1|2*2=2
Thread-5|10*2=10
Thread-3|6*2=6
Thread-4|8*2=8
Thread-2|4*2=4
Thread-3|18*3=18
Thread-1|6*3=6
Thread-5|30*3=30
Thread-2|12*3=12
Thread-4|24*3=24
Thread-4|96*4=96
Thread-2|48*4=48
Thread-5|120*4=120
Thread-1|24*4=24
Thread-3|72*4=72
Thread-5|600*5=600
Thread-4|480*5=480
Thread-3|360*5=360
Thread-1|120*5=120
Thread-2|240*5=240
Thread-1|TaskDurationInMillis = 765
Thread-3|TaskDurationInMillis = 765
Thread-4|TaskDurationInMillis = 765
Thread-5|TaskDurationInMillis = 765
Thread-2|TaskDurationInMillis = 765

现在让我使用join()for线程:

    val timeInMillis = System.currentTimeMillis()
    ThreadUtils.startNewThread(Runnable {
        for (i in 1..5) {
            val t = Thread(Runnable {
                Thread.sleep(50)
                var a = i
                kotlin.io.println(Thread.currentThread().name + "|" + "a=$a")
                Thread.sleep(200)
                for (j in 1..5) {
                    a *= j
                    Thread.sleep(100)
                    kotlin.io.println(Thread.currentThread().name + "|" + "$a*$j=$a")
                }
                kotlin.io.println(Thread.currentThread().name + "|TaskDurationInMillis = " + (System.currentTimeMillis() - timeInMillis))
            })
            t.start()
            t.join()
        }
    })

结果:

Thread-1|a=1
Thread-1|1*1=1
Thread-1|2*2=2
Thread-1|6*3=6
Thread-1|24*4=24
Thread-1|120*5=120
Thread-1|TaskDurationInMillis = 815
Thread-2|a=2
Thread-2|2*1=2
Thread-2|4*2=4
Thread-2|12*3=12
Thread-2|48*4=48
Thread-2|240*5=240
Thread-2|TaskDurationInMillis = 1568
Thread-3|a=3
Thread-3|3*1=3
Thread-3|6*2=6
Thread-3|18*3=18
Thread-3|72*4=72
Thread-3|360*5=360
Thread-3|TaskDurationInMillis = 2323
Thread-4|a=4
Thread-4|4*1=4
Thread-4|8*2=8
Thread-4|24*3=24
Thread-4|96*4=96
Thread-4|480*5=480
Thread-4|TaskDurationInMillis = 3078
Thread-5|a=5
Thread-5|5*1=5
Thread-5|10*2=10
Thread-5|30*3=30
Thread-5|120*4=120
Thread-5|600*5=600
Thread-5|TaskDurationInMillis = 3833

很明显,当我们使用时join

  1. 线程按顺序运行。
  2. 第一个采样花费765毫秒,而第二个采样花费3833毫秒。

我们防止阻塞其他线程的解决方案是创建ArrayList:

val threads = ArrayList<Thread>()

现在,当我们要启动一个新线程时,我们最多将其添加到ArrayList中:

addThreadToArray(
    ThreadUtils.startNewThread(Runnable {
        ...
    })
)

addThreadToArray函数:

@Synchronized
fun addThreadToArray(th: Thread) {
    threads.add(th)
}

startNewThreadfunstion:

fun startNewThread(runnable: Runnable) : Thread {
    val th = Thread(runnable)
    th.isDaemon = false
    th.priority = Thread.MAX_PRIORITY
    th.start()
    return th
}

在需要的地方检查线程的完成情况,如下所示:

val notAliveThreads = ArrayList<Thread>()
for (t in threads)
    if (!t.isAlive)
        notAliveThreads.add(t)
threads.removeAll(notAliveThreads)
if (threads.size == 0){
    // The size is 0 -> there is no alive threads.
}
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.