1、简介

CountDownLatch是JUC包下的实现类,具体的使用场景见第2节。CountDownLatch的底层是AQS的共享锁的实现,在实际开发中该类有些鸡肋,通常用CompletableFuture这种更强大的异步任务类来实现业务场景,这里主要做了解,实际开发中并不推荐使用CountDownLatch。

1.1 常用api

  1. // 构造方法,入参是一个int类型的值
  2. public CountDownLatch(int count);
  3. // 阻塞当前线程,将当前线程加入到阻塞队列
  4. public void await() throws InterruptedException();
  5. // 重载上一个方法,加入timeout超时时间,在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行
  6. public boolean await(long timeout, TimeUnit unit);
  7. // 对计数器进行减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程
  8. public void countDown();
  9. // 返回当前计数器的值
  10. public long getCount();

2、使用场景

这些场景也是我在网上看的,实际开发中我没有用过CountDownLatch,CountDownLatch对应的使用场景,JDK8中的CompletableFuture类的api都可以覆盖,而且功能更加强大,推荐使用CompletableFuture。

2.1 “发令枪”场景

场景描述:让多个线程等待,满足某一条件后让多个线程在同一时刻一并执行。就像田径项目中裁判员的号令枪一样,号令枪一响,所有准备状态的运动员一起冲出去。
demo如下:

public class CdlMainA {
    public static void main(String[] args) {
        int threadNum = 5;
        CountDownLatch countDownLatch = new CountDownLatch(1);

        for (int i = 0; i < threadNum; ++i) {
            new Thread(() -> {
                try {
                    //准备完毕的运动员都阻塞在这,等待号令
                    countDownLatch.await();
                    // 模拟每个运动员在号令枪响后的出发动作
                    System.out.println(Thread.currentThread().getName() + " 开始执行任务");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }

        try {
            // 模拟主线程要做的业务
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 模拟裁判员的号令枪发射
        countDownLatch.countDown();
    }
}

试验结果:主线程阻塞5秒钟,5秒钟后5个线程同时执行逻辑。

2.2 多个异步任务汇总

比如要查询一些指标,将查询后的若干指标拼接成返回体返回。为了提高接口响应速度,将这些查询请求做成异步的,等所有异步查询请求都查询到结果后,再将结果拼接成返回体返回,此时就需要主线程等待所有异步任务返回后再执行的场景。
demo如下:

public class CdlMainB {
    public static void main(String[] args) {
        CountDownLatch countDownLatch=new CountDownLatch(4);

        new Thread(() -> {
            try {
                // 模拟每个异步任务的执行
                Thread.sleep(2000);
                // 标记每个异步任务执行结束
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " 异步任务完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                // 模拟每个异步任务的执行
                Thread.sleep(3000);
                // 标记每个异步任务执行结束
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " 异步任务完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                // 模拟每个异步任务的执行
                Thread.sleep(4000);
                // 标记每个异步任务执行结束
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " 异步任务完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                // 模拟每个异步任务的执行
                Thread.sleep(5000);
                // 标记每个异步任务执行结束
                countDownLatch.countDown();
                System.out.println(Thread.currentThread().getName() + " 异步任务完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        try {
            System.out.println(Thread.currentThread().getName() + " 主线程等待异步任务完成...");
            // 主线程阻塞
            countDownLatch.await();
            System.out.println(Thread.currentThread().getName() + " 所有异步任务完成,主线程开始处理业务");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如下:
image.png
异步任务执行期间,主线程处于阻塞状态,等所有异步任务都完成后,主线程才开始处理业务。

3、源码浅析

CountDownLatch的源码相比ReentrantLock简单太多,主要关注一下几个方法和类。

3.1 Sync内部类

private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

Sync是一个内部实现类,继承了AQS类(AQS相关参考我这篇文章:https://www.yuque.com/docs/share/da731dfb-7471-43a6-87d4-11d6425d78bd?# 《AQS》)。该类主要提供两个方法:tryAcquireShared方法(加锁)和tryReleaseShared方法(解锁),这两个方法都是对AQS抽象类中同名方法的覆写。

3.2 await方法(加锁)

public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}

底层是由AQS抽象类的实现方法实现。

3.3 countDown方法(解锁)

public void countDown() {
        sync.releaseShared(1);
}

底层是由AQS抽象类的实现方法实现。

参考

CountDownLatch的两种常用场景
CountDownLatch的理解和使用