java.util.concurrent.CountDownLatch是JDK 1.5提供的一个同步辅助类:在一组正在其他线程中的操作执行完成之前,它允许一个或多个线程一直等待。

  初始化CountDownLatch时需要指定计数。通过调用countDown方法使当前计数到达零之前,await方法会一直阻塞。之后,所有等待的线程会被释放,await后面的操作也会立即执行。因为计数无法被重置,所以这种操作只会出现一次。如果需要重置计数,请考虑使用java.util.concurrent.CyclicBarrier。

  CountDownLatch是一个通用同步工具,它有很多用途。用1初始化的CountDownLatch可以用作一个简单的开/关锁存器或入口:在某一线程调用countDown打开入口前,所有调用await的线程都一直在入口处等待。用N(N>=1)初始化的 CountDownLatch可以使一个线程在N个线程完成某项操作之前一直等待,或者使其在某项操作完成N次之前一直等待。

  CountDownLatch的一个有用特性是,在计数达到0之前,它不会阻塞调用countDown方法的线程继续执行,它只是阻止所有调用await的线程继续执行await后面的操作。

CountDownLatch有两种典型用法:

  1. 有两个计数器,一个启动信号,一个完成信号。
  2. 将一个问题分成N个部分,需要执行的N个子部分定义为Runnable,然后将所有Runnable加入到Executor队列中。当所有的N个子部分完成后,等待的线程将会通过await方法继续执行后续操作。

两个计数器

示例代码:

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch startSignal = new CountDownLatch(1);// 先行条件
        CountDownLatch doneSignal = new CountDownLatch(N);

        for (int i = 0; i < N; ++i) {
            new Thread(new Worker(startSignal, doneSignal)).start();
        }

        doSomethingElse();
        startSignal.countDown();// 开始
        doSomethingElse();
        doneSignal.await();// 等待所有操作结束
    }
}

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

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

    @Override
    public void run() {
        try {
            startSignal.await();// 等待先行条件结束
            doWork();
            doneSignal.countDown();
        } catch (InterruptedException ex) {
        }
    }

    void doWork() {...}
}

  在示例代码中,startSignal为先决条件,此处初始计数为1,是一个简单的开关。比如田径比赛中的百米跑,发令枪响前,运动员都在等待;发令枪响后,运动员开始比赛,等到最后一名运动员到达终点,比赛结束。

简单的代码实现为:

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

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch begin = new CountDownLatch(1);
        final CountDownLatch end = new CountDownLatch(3);
        final ExecutorService exec = Executors.newFixedThreadPool(3);

        for (int index = 0; index < 3; index++) {
            exec.submit(new Runner(index + 1, begin, end));
        }

        System.out.println("各就各位。。。");
        System.out.println("预备。。。");
        System.out.println("嘭。。。");
        begin.countDown();
        end.await();
        System.out.println("比赛结束,准备颁奖。");
        exec.shutdown();
    }
}

class Runner implements Runnable {
    private int no;
    private CountDownLatch begin;
    private CountDownLatch end;

    public Runner(int no, CountDownLatch begin, CountDownLatch end) {
        this.no = no;
        this.begin = begin;
        this.end = end;
        System.out.println("No." + no + "到达起跑线。");
    }

    @Override
    public void run() {
        try {
            begin.await();// 等待发令枪响
            System.out.println("No." + no + "向前飞奔着。。。");
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));// 奔跑的过程中。。。
        } catch (InterruptedException e) {
        }
        System.out.println("No." + no + "到达终点。");
        end.countDown();
    }
}

  引申开来,开始定义一系列的执行的链条,第一个没有先决条件,直接执行,第二个以第一个为先决条件,以此类推。我是懒人,不做太多赘述,我一个朋友的文章中写的不错:利用CountDownLatch同步辅助类进行线程同步

一个计数器

  一个计数器的情况自己感觉情况比较单一,就是主线程等待子线程结束,再继续执行。这里的主线程、子线程是相对而言的,可能主线程本身是另一个线程的子线程。

示例代码:

public class CountDownLatchTest6 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch doneSignal = new CountDownLatch(3);
        ExecutorService e = Executors.newCachedThreadPool();

        for (int i = 0; i < 3; ++i) {
            e.execute(new Worker(doneSignal, i));
        }
        doSomethingElse();
        doneSignal.await();// 等待子线程结束
        doSomethingElse();
        e.shutdown();
    }
}

class Worker implements Runnable {
    private final CountDownLatch doneSignal;
    private final int id;

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

    @Override
    public void run() {
        doWork();
        doneSignal.countDown();
    }

    void doWork() {...}
}

  示例代码中的doSomethingElse方法可以是一些业务逻辑代码,根据具体功能发生变化。

  对于这种方式可以参看上一篇中关于老板和工人的例子中的第二种解决方法主线程等待子线程结束,这里也不做赘述。