在前面的文章中我们了解了,AQS
和ReentrantLock
的实现原理。在本篇文章中我们介绍Java中的另一个同步工具CountDownLatch
。
CountDownLatch
也是一个比较常用的同步工具,其也是在AQS
的基础上实现的。
1 基本使用
CountDownLatch
是也是Java中一个比较重要的同步工具,当我们希望主线程等待多条子线程逻辑处理完后再往下执行时我们可以使用这个工具类,其基本使用如下:
/**
* Create By IntelliJ IDEA
*
* @author: XieHua
* @date: 2021-06-07 20:33
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i ++) {
Thread thread = new Thread(new MyRunnable(countDownLatch), "THREAD" + i);
thread.start();
}
countDownLatch.await();
}
public static class MyRunnable implements Runnable {
private CountDownLatch countDownLatch;
public MyRunnable (CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName);
countDownLatch.countDown();
}
}
}
上面这段代码中,我们创建了一个数量为5的CountDownLatch
对象,然后创建五个子线程各自调用countDownLatch.countDown()
方法,调用countDownLatch.await()
方法阻塞主线程,运行程序我们发现时执行完五个子线程才会结束,运行结果如下:
2 类结构
CountDownLatch
的类结构是比较简单的,该类中有个Sync
的内部类。Synck
继承自AQS
,CountDownLatch
的功是由该类实现的,其类图如下:
3 实现原理
通过最开始的实例,我们知道CountDownLatch
的主要方法有
- 构造函数
countDown
await
接下来我们依次看下这三个方法的逻辑
3.1 构造函数
构造函数的源码如下:
public CountDownLatch(int count) {
// count小于0抛出异常
if (count < 0) throw new IllegalArgumentException("count < 0");
// 创建Sync对象
this.sync = new Sync(count);
}
Sync(int count) {
// 设置state字段的值为count
setState(count);
}
在构造方法中没什么逻辑,只是设置下state
的值,通过该方法我们发现,在CountDownLatch
中是使用AQS
中的state字段记录需要等待的线程数。
3.2 await
这个方法是时主线程阻塞,其源码如下:
public void await() throws InterruptedException {
// 调用AQS中的方法,在该方法中会调用tryAcquireShared方法
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 线程被中断过抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared也是一个模板方法 需要使用的子类去实现
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
// CountDownLatch.Sync中的实现
protected int tryAcquireShared(int acquires) {
// state = 0代表获取成功
return (getState() == 0) ? 1 : -1;
}
通过上面的源码,我们发现CountDonwLatch
中使用的是AQS
的共享模式,由于在AQS
源码介绍的那篇文章中我们没有说共享模式的代码,在这里就简单的点进去看一下,doAcquiresSharedInterruptibly
的源码如下:
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 在阻塞队列中插入共享模式的节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
// 自旋
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 尝试获取共享锁
int r = tryAcquireShared(arg);
// 大于0代表获取成功
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 会将前置节点的状态值修改为 -1 并调用LockSupport.park方法阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这段方法逻辑也是比较简单的,其过程如下:
1、尝试获取锁
2、获取锁失败,初始化阻塞队列并将线程以共享模式插入队列中
3、 自旋的模式修改前置节点状态为-1 并阻塞当前线程
3.3 countDown方法
这个方法用于减少count值,当值降低为0时代表释放锁成功,唤醒主线程,其源码如下:
public void countDown() {
// 调用AQS中的释放共享锁的方法,在releaseShared方法中会调用tryReaseShared方法
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// 模板方法 需要在CountDownLatch中实现
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
// 自旋
for (;;) {
// 当前的count
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
// CAS操作修改state的值
if (compareAndSetState(c, nextc))
// 修改后的值为0代表释放成功
return nextc == 0;
}
}
private void doReleaseShared() {
// 自旋
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒下个节点 LockSupport.unpark
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
4 总结
至此CountDownLatch
的原理便介绍完成了,代码逻辑也是比较简单的,理解了AQS
的处理逻辑,这些在其基础上实现的功能,都会很好理解。
在CountDownLatch
中使用AQS
中的state
来记录需要等待的线程数量,调用await
方法将主线程添加进AQS
的阻塞队列,调用countDown
方法减少state
的值,当state
减少到0时再唤醒主线程。
如果感觉对您有帮助,欢迎关注下公众号,您的关注是我更新的最大动力~