前言

本文隶属于专栏《100个问题搞定Java并发》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!

本专栏目录结构和参考文献请见100个问题搞定Java并发

正文

CountDownLatch 是一个非常实用的多线程控制工具类。

“CountDown”在英文中意为倒计数, Latch 意为门闩的意思。

这里简单地称之为倒计数器。

在这里,门闩的含义是把门锁起来,不让里面的线程跑出来。

因此,这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计数结束,再开始执行。

对于倒计数器,一种典型的场景就是火箭发射。

在火箭发射前,为了保证万无一失,往往还要对各项设备、仪器进行检查。

只有等所有检查都完成后,引擎才能点火。

这种场景就非常适合使用 CountDownLatch 。

它可以使点火线程等待所有检査线程全部完工后再执行 CountDownLatch 的构造函数接收一个整数作为参数,即当前这个计数器的计数个数。

public CountDownLatch(int count) 

源码(JDK8)

/**
 * 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
 *
 * CountDownLatch 用给定的计数初始化。 
 *
 * await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 
 *
 * 这是一个一次性的现象 - 计数无法重置。 
 *
 * 如果您需要重置计数的版本,请考虑使用CyclicBarrier 。
 *
 * CountDownLatch是一种通用的同步工具,可用于多种用途。 
 *
 * 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 
 *
 * 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
 *
 * CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程可以通过。
 *
 * 内存一致性效果:直到计数调用之前达到零,在一个线程操作countDown() happen-before 在另一个线程执行 await() 并成功返回。
 */
public class CountDownLatch

官方示例

package com.shockang.study.java.concurrent.aqs;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * 示例用法:这是一组类,其中一组工作线程使用两个倒计时锁存器:
 * <p>
 * 第一个是启动信号,防止任何工作人员进入,直到驾驶员准备好继续前进;
 * <p>
 * 第二个是完成信号,允许司机等到所有的工作人员完成。
 */
public class Driver { // ...
    public static final int N = 10;

    void main() throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(N);

        for (int i = 0; i < N; ++i) // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();

        doSomethingElse();            // don't let run yet
        startSignal.countDown();      // let all threads proceed
        doSomethingElse();
        doneSignal.await();           // wait for all to finish
    }

    private void doSomethingElse() {
    }

}

class Worker implements Runnable {
    private final CountDownLatch startSignal;
    private final CountDownLatch doneSignal;

    Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
        this.startSignal = startSignal;
        this.doneSignal = doneSignal;
    }

    public void run() {
        try {
            startSignal.await();
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        } // return;
    }

    void doWork() {
    }
}

/**
 * 另一个典型的用法是将问题划分为N个部分,用一个Runnable来描述每个部分,该Runnable执行该部分并在锁存器上倒计时,并将所有Runnables排队到执行器。
 *
 * 当所有子部分完成时,协调线程将能够通过等待。 (当线程必须以这种方式反复倒数时,请改用CyclicBarrier )
 */
class Driver2 { // ...
    public static final int N = 10;

    void main() throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(N);
        Executor e = Executors.newFixedThreadPool(N);

        for (int i = 0; i < N; ++i) // create and start threads
            e.execute(new WorkerRunnable(doneSignal, i));

        doneSignal.await();           // wait for all to finish
    }
}

class WorkerRunnable implements Runnable {
    private final CountDownLatch doneSignal;
    private final int i;

    WorkerRunnable(CountDownLatch doneSignal, int i) {
        this.doneSignal = doneSignal;
        this.i = i;
    }

    public void run() {
        try {
            doWork(i);
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        } // return;
    }

    void doWork(int i) throws InterruptedException {
    }
}

实践

下面这个简单的示例,演示了 CountDownLatch 的使用方法。

package com.shockang.study.java.concurrent.aqs;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 倒数计时器
 *
 * @author Shockang
 */
public class CountDownLatchDemo implements Runnable {
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {
        try {
            //模拟检查任务
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            exec.submit(demo);
        }
        //等待检查
        end.await();
        //发射火箭
        System.out.println("Fire!");
        exec.shutdown();
    }
}

上述代码第 14 行生成一个 CountDownLatch 实例,计数数量为 10 ,这表示需要 10 个线程完成任务后等待在 CountDownLatch 上的线程才能继续执行。

代码第 23 行使用了 CountDownLatch.countDown() 方法,也就是通知 CountDownLatch ,一个线程已经完成了任务,倒计数器减 1 。

第 35 行使用 CountDownLatch.await() 方法,要求主线程等待所有检査任务全部完成,待 10 个任务全部完成后,主线程才能继续执行。

上一篇 下一篇