前言

相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行。

案例

用一个最简单的案例引出我们的主角

public class CountDownLatchDemo {

    public void run(CountDownLatch countDownLatch) {
System.out.println(Thread.currentThread().getName() + "就位");
countDownLatch.countDown();
} public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
CountDownLatch countDownLatch = new CountDownLatch(5);
IntStream.rangeClosed(0, 4)
.forEach(num -> executorService
.execute(() -> countDownLatchDemo.run(countDownLatch))
); countDownLatch.await();
System.out.println("已到齐");
} /**
* 输出:
* pool-1-thread-2就位
* pool-1-thread-5就位
* pool-1-thread-4就位
* pool-1-thread-3就位
* pool-1-thread-1就位
* 已到齐
*/
}

源码分析

看源码前最好先熟悉下 AQS 的大致结构,之前有两篇文章仅供参考,大致熟悉下即可

Java读源码之ReentrantLock

Java读源码之ReentrantLock(2)

在看 AQS 的 Node 节点的时候看到有共享模式和独占模式,ReentrantLock 用了独占模式,CountDownLatch 正式用了共享模式,相信看完能够对 AQS 有更深的理解。

初始化

  • CountDownLatch#CountDownLatch
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
// 可以看到 CountDownLatc 内部也实现了一个 AQS
this.sync = new Sync(count);
}
  • CountDownLatch.Sync#Sync
Sync(int count) {
// 直接拿了 count 把锁(当前线程可重入 Sync 锁 count 次)
setState(count);
}

等待

  • CountDownLatch#await
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
  • AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 注意,持有锁的线程被中断是直接抛异常的
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquireShared很简单,如果全员到齐了返回1,其他时候都返回 -1
if (tryAcquireShared(arg) < 0)
// 所以没到齐前都会以共享模式入同步队列
doAcquireSharedInterruptibly(arg);
}
  • AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// addWaiter之前看过,作用就是把节点放到同步队列的末尾,但是这里节点类型是共享模式
// 值得注意的是,模式是存在节点的 nextWaiter 中,所以不管 nextWaiter 可能三种情况 1。独占模式的空节点 2.共享模式的空节点 3。Condition条件队列的下一个节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
// 下面的自旋和 acquireQueued 方法基本一模一样,重点看下区别
try {
for (;;) {
final Node p = node.predecessor();
// 如果前驱节点是 head 说明没人排队
if (p == head) {
// 再次尝试
int r = tryAcquireShared(arg);
// 只有调用了足够次数countDown,栅栏才会打开
if (r >= 0) {
// 这里的 r 一定为 1,会一个一个唤醒所有节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 一般入队肯定有人排队的,之前也看过,主要作用,通知前驱节点,然后挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
  • AbstractQueuedSynchronizer#setHeadAndPropagate
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate = 1 次判断一定为 true
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// CountDownLatch节点所有节点都是共享模式,一定满足
if (s == null || s.isShared())
// 直接唤醒下一个
doReleaseShared();
}
}
  • AbstractQueuedSynchronizer#doReleaseShared
private void doReleaseShared() {
for (;;) {
Node h = head;
// 同步队列不为空则进入
if (h != null && h != tail) {
int ws = h.waitStatus;
// 如果有节点需要被唤醒
if (ws == Node.SIGNAL) {
// 不断重试 CAS 吧 头节点设为全新
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒下一个节点
unparkSuccessor(h);
}
// 到这里 下一个已经唤醒了,把节点状态设置为 PROPAGATE,说明是共享状态唤醒的
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 直到头节点变化结束,也就是下一个一个被唤醒了,然后再由下一个接着唤醒
if (h == head)
break;
}
}

签到

看等待过程,栅栏打开后,所有共享模式的节点会一个一个的唤醒,让我们一起看看如何打开栅栏并唤醒第一个节点。

  • AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 这个方法和等待时候自悬的一样,用于唤醒第一个节点
doReleaseShared();
return true;
}
return false;
}
  • CountDownLatch#tryReleaseShared
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
// c == 0 说明栅栏已经打开过了,CountDownLatch 是一次性的,直接false
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
// cas 递减状态,达到0的时候返回 true 栅栏打开
return nextc == 0;
}
}

最新文章

  1. [转载]Python中的sys模块
  2. [Unity2D]2D Mobile Joystick
  3. JS模块式开发
  4. JavaScript 同源策略
  5. 编写留言板是遇到的mysql中文乱码问题
  6. ubuntu亮度无法自动调节终极解决方案
  7. Linux学习笔记22——线程属性(转)
  8. hdu4389(数位dp)
  9. 06 Activity显示跳转
  10. mysql-笔记-数据类型
  11. 2.10 while循环应用
  12. PostgreSQL自学笔记:5 数据类型和运算符
  13. 对java高级程序员有益的十本书
  14. 如何给a标签绑定ajax事件
  15. IOS中为tableViewCell增加右侧标记(选中或者更多)
  16. golang包管理工具及环境管理工具;如何下载外网的依赖包
  17. 解决PCI Geomatica 无法卸载的问题
  18. Java中的监听器
  19. pdf 下载整理
  20. mindmanager2018优化

热门文章

  1. Python——迭代器的几个高级用法
  2. Mysql常用sql语句(二)- 操作数据表
  3. 【笔记3-31】Python语言基础-元组tuple
  4. linux入门系列20--Web服务之LNMP架构实战
  5. Linux时间和现实时间不同步解决方案
  6. SVM | 支持向量机原理讲解(二)
  7. 「MoreThanJava」当大学选择了计算机之后应该知道的
  8. openssl与java(读取加密码的密钥)
  9. linux 之虚拟机的安装与介绍
  10. ML-Agents(六)Tennis