大白话讲并发编程之 CountDownLatch 源码
一、什么是 CountDownLatch ?
CountDownLatch
是 Doug Lea
写的一个并发工具类,其中的关键方法是 await()
和 countDown()
。
它有两个比较典型的应用:
- 主线程等待其他线程任务完成后再执行。典型案例:服务启动时,会等一系列组件加载完后再执行;
代码如下(示例)
// 先countDown(),后await()
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(i+""){
@Override
public void run() {
Thread.sleep(1000); //此处 try-catch省略了
System.out.println("线程-"+Thread.currentThread().getName()+"执行完了");
countDownLatch.countDown();
}
}.start();
}
System.out.println("主线程等待中...");
countDownLatch.await();
System.out.println("主线程开始执行...");
}
# 控制台输出:
// 主线程等待中...
// 线程-3执行完了
// 线程-1执行完了
// 线程-0执行完了
// 线程-4执行完了
// 线程-2执行完了
// 主线程开始执行...
- 模拟并发,使得多个线程同时执行。典型案例:作压测工具。
代码如下(示例)
// 先await(),后countDown()
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(i+""){
@Override
public void run() {
try {
System.out.println("线程-"+ Thread.currentThread().getName()+"开始等待...");
countDownLatch.await();
System.out.println("线程-"+Thread.currentThread().getName()+"--"+System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
System.out.println("等待所有线程进入await()...");
Thread.sleep(3000);
countDownLatch.countDown();
}
# 控制台输出:
// 等待所有线程进入await()...
// 线程-0开始等待...
// 线程-4开始等待...
// 线程-1开始等待...
// 线程-2开始等待...
// 线程-3开始等待...
// 线程-4--1636700139174
// 线程-1--1636700139174
// 线程-0--1636700139174
// 线程-3--1636700139174
// 线程-2--1636700139174
二、源码解析
1.先看构造器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
CountDownLatch
只有一个构造器,参数最终会赋给状态器 state
( AQS
中最重要的变量)
2.await() 方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
如果看过我之前分享的 Semphore
源码,应该能够发现他们的代码结构是一样的,都是调用acquireSharedInterruptibly(1)
这个方法,唯一的区别是他们都对 tryAcquireShared(arg)
进行了重写。
话不多说,直接看源码:
await()
方法调用了 acquireSharedInterruptibly(...)
方法,而这个方法里有两个方法。先看tryAcquireShared(...)
方法,里面是一个三元运算符,判断状态器(state)是否为零,这里的状态器其实就是我们构造器传入的参数。state
为零则返回 1,获取锁成功,接着不会执行后面的方法了,直接跳出 await()
;state
不为零则返回 -1 ,表示获取锁失败,开始进入 doAcquireSharedInterruptibly(arg)
方法,这个方法其实就是入队阻塞。与Semaphore
调用的是同一个方法的,之前已经细讲过了,所以这里我就不多说了。想要了解这个方法的执行过程可以去看我的前篇文章 Semaphore
。
3.countDown() 方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
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;
}
}
每调用一次countDown()
方法,状态器就会 -1
,等状态器为 0
时就会调用doReleaseShared()
方法将线程唤醒。
总结
那么 CountDownLatch
到这里也就分享完了,相对 ReentrantLock
和 Semaphore
来说,这个类的确简单很多。而且 CountDownLatch
和 Semaphore
代码调用的很多方法都是一致的,只是两个对两个抽象方法 tryAcquireShared(...)
和 tryReleaseShared(...)
重写了下。