System.Threading.CountdownEvent 是一个同步基元，它在收到一定次数的信号之后，将会解除对其等待线程的锁定。 CountdownEvent 专门用于以下情况：您必须使用 ManualResetEvent 或 ManualResetEventSlim，并且必须在用信号通知事件之前手动递减一个变量。 例如，在分叉/联接方案中，您可以只创建一个信号计数为 5 的 CountdownEvent，然后在线程池上启动五个工作项，并且让每个工作项在完成时调用 Signal。 每次调用 Signal 时，信号计数都会递减 1。 在主线程上，对 Wait 的调用将会阻塞，直至信号计数为零。 CountdownEvent 具有这些附加功能： •可通过使用取消标记来取消等待操作。 •创建实例之后可以递增它的信号计数。 •通过调用 Reset 方法，可在 Wait 返回之后重用实例。 •实例公开 WaitHandle 以便与其他 .NET Framework 同步 API（例如 WaitAll）进行集成。private void CountEventTest() { CountdownEvent count = new CountdownEvent();//创建5个数量 Task[] task = new Task[]; count.Reset();//重置为4个数量 Action act = () => { MessageBox.Show("ok"); count.Signal();//通知已经有一个线程完成了 }; for (int i = ; i < task.Length; ++i) { task[i] = new Task(act); task[i].Start(); } count.Wait();//等待4个线程都完成 MessageBox.Show("end"); } 简单的说就是先设置多少个数量，然后Signal通知一个已经完成，Wait等待所有数量全部完成，则继续往下运行上述例子：先弹出4个OK，然后一个一个点掉，最后一个点掉的时候 弹出 end。CountdownEvent 和 Barrier 非常接近类似来源：https://www.bbsmax.com/A/gVdnM015Wl/
Java多线程之以7种方式让主线程等待子线程结束
一、while循环对于“主线程如何获取子线程总运行时间”的问题，最开始想到的是使用while循环进行轮询：Thread t = new Thread(() -> { //子线程进行字符串连接操作 int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); }); //开始计时 long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); long end = 0; while(t.isAlive() == true){ end = System.currentTimeMillis(); try { Thread.sleep(10); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); 二、Thread的join()方法使用join()方法，join()方法的作用，是等待这个线程结束；（t.join()方法阻塞调用此方法的线程(calling thread)，直到线程t完成，此线程再继续long start = System.currentTimeMillis(); System.out.println("start = " + start); t1.start(); try { t.join();//注意这里 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - Start:" + (end - start)); 三、synchronized的等待唤醒机制Object lock = new Object(); Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); synchronized (lock) {//获取对象锁 lock.notify();//子线程唤醒 } }); //计时 long start = System.currentTimeMillis(); System.out.println("start = " + start); //启动子线程 t.start(); try { synchronized (lock) {//这里也是一样 lock.wait();//主线程等待 } } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); 四、CountDownLatch使用java.util.concurrent包下的CountDownLatch类，简单来说，CountDownLatch类是一个计数器，可以设置初始线程数（设置后不能改变），在子线程结束时调用countDown()方法可以使线程数减一，最终为0的时候，调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态，进入RUNNABLE从而继续执行int threadNumber = 1; final CountDownLatch cdl = new CountDownLatch(threadNumber);//参数为线程个数 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); cdl.countDown();//此方法是CountDownLatch的线程数-1 }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); //线程启动后调用countDownLatch方法 try { cdl.await();//需要捕获异常，当其中线程数为0时这里才会继续运行 }catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); 五、Future线程池的submit()的返回对象Future接口有一个get()方法也可以阻塞当前线程（其实该方法主要用途是获取子线程的返回值）ExecutorService executorService = Executors.newFixedThreadPool(1); Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); }); long start = System.currentTimeMillis(); System.out.println("start = " + start); Future future = executorService.submit(t);//子线程启动 try { future.get();//需要捕获两种异常 }catch (InterruptedException e){ e.printStackTrace(); }catch (ExecutionException e){ e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); executorService.shutdown(); 这里, ThreadPoolExecutor 是实现了 ExecutorService的方法, sumbit的过程就是把一个Runnable接口对象包装成一个 Callable接口对象, 然后放到 workQueue里等待调度执行. 当然, 执行的启动也是调用了thread的start来做到的, 只不过这里被包装掉了. 另外, 这里的thread是会被重复利用的, 所以这里要退出主线程, 需要执行以下shutdown方法以示退出使用线程池.六、BlockingQueue在concurrent包中，还提供了BlockingQueue（队列）来操作线程，BlockingQueue的主要的用法是在线程间安全有效的传递数据BlockingQueue queue = new ArrayBlockingQueue(1);//数组型队列，长度为1 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); try { queue.put("OK");//在队列中加入数据 } catch (InterruptedException e) { e.printStackTrace(); } }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); try { queue.take();//主线程在队列中获取数据，take()方法会阻塞队列，ps还有不会阻塞的方法 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); 七、CyclicBarrierCyclicBarrier字面意思回环栅栏，通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后，CyclicBarrier可以被重用。CyclicBarrier barrier = new CyclicBarrier(2);//参数为线程数 Thread t = new Thread(() -> { int num = 1000; String s = ""; for (int i = 0; i < num; i++) { s += "Java"; } System.out.println("t Over"); try { barrier.await();//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); try { barrier.await();//也阻塞,并且当阻塞数量达到指定数目时同时释放 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - start = " + (end - start)); 实际是上面这种方法是不太严谨的，因为在子线程阻塞之后如果还有代码是会继续执行的，当然本例中后面是没有代码可执行了，可以近似理解为是子线程的运行时间。来源：https://www.cnblogs.com/lixin-link/p/10998058.html