上一篇文章说了一下CountDownLatch的使用方法。这篇文章就从源码层面说一下await() 的原理。

我们已经知道await 能够让当前线程处于阻塞状态,直到锁存器计数为零(或者线程中断)。

下面是它的源码。

end.await();

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

sync 是CountDownLatch的内部类。下面是它的定义。

private static final class Sync extends AbstractQueuedSynchronizer {
  ...
}

它继承了AbstractQueuedSynchronizer。AbstractQueuedSynchronizer 这个类在java线程中属于一个非常重要的类。

它提供了一个框架来实现阻塞锁,以及依赖FIFO等待队列的相关同步器(比如信号、事件等)。

继续走下去,就跳到 AbstractQueuedSynchronizer 这个类中。

sync.acquireSharedInterruptibly(1);

public final void acquireSharedInterruptibly(int arg) //AbstractQueuedSynchronizer
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}

这里有两个判断,首先判断线程是否中断,然后再进行下一个判断,这里我们主要看看第二个判断。

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

需要注意的是 tryAcquireShared 这个方法是在Sync 中实现的。

AbstractQueuedSynchronizer 中虽然也有对它的实现,但是默认的实现是抛一个异常。

tryAcquireShared 这个方法是用来查询当前对象的状态是否能够被允许获取锁。

我们可以看到Sync 中是通过判断state 是否为0 来返回对应的 int 值的。

那么 state 又代表什么?

/**
* The synchronization state.
*/
private volatile int state;

上面代码很清楚的表明 state 是表示同步的状态 。

需要注意的是 state 使用 volatile 关键字修饰。

volatile 关键字能够保证 state 的修改立即被更新到主存,当有其他线程需要读取时,会去内存中读取新值。

也就是保证了state的可见性。是最新的数据。

走到这里 state 是多少呢?

这里我们就需要看一看CountDownLatch 的 构造函数了。

CountDownLatch end = new CountDownLatch(2);

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

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

原来构造函数中的数字就是这个作用啊,用来set state 。

所以我们这里state == 2 了。tryAcquireShared 就返回 -1。进入到下面

doAcquireSharedInterruptibly(arg);

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);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}

OK,这段代码有点长,里面还调用了几个函数。我们一行一行的看。

第一行 出现了一个新的类 Node。

Node 是AQS(AbstractQueuedSynchronizer)类中的内部类,定义了一种链式结构。如下所示。

     +------+  prev +-----+       +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+

千万记住这个结构。

第一行代码中还有一个方法 addWaiter(Node.SHARED) 。

addWaiter(Node.SHARED)  //Node.SHARED  表示该结点处于共享模式

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; // private transient volatile Node tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

首先是构造了一个Node,将当前的线程存进去了,模式是共享模式。

tail 表示 这个等待队列的队尾,此刻是null. 所以 pred == null ,进入到enq(node) ;

enq(node)

private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

同样tail 为 null , 进入到 compareAndSetHead 。

compareAndSetHead(new Node())

/**
* CAS head field. Used only by enq.
*/
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

这是一个CAS操作,如果head 是 null 的话,等待队列的 head 就会被设置为 update 的值,也就是一个新的结点。

tail = head;  那么此时 tail 也不再是null了。进入下一次的循环。

这次首先将node 的 prev 指针指向 tail ,然后通过一个CAS 操作将node 设置为尾部,并返回了队列的 tail ,也就是 node 。

等待队列的模型变化如下

           +------+  prev      +----------------+
head(tail) | | <---- node | currentThread |
+------+ +----------------+ ↓ +------+ prev +----------------+
head | | <---- node(tail) | currentThread |
+------+ +----------------+

ok,到了这里await 方法 就返回了,是一个 thread 等于当前线程的Node。

返回到 doAcquireSharedInterruptibly(int arg) 中,进入下面循环。

for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}

这个时候假设state 仍然大于0,那么此时 r < 0,所以进入到 shouldParkAfterFailedAcquire 这个方法 。

shouldParkAfterFailedAcquire(p, node)

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //static final int SIGNAL = -1;
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

/**
* CAS waitStatus field of a node.
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}

可以看到 shouldParkAfterFailedAcquire  也是一路走,走到 compareAndSetWaitStatus。

compareAndSetWaitStatus 将 prev 的 waitStatus 设置为 Node.SIGNAL 。

Node.SIGNAL 表示后续结点中的线程需要被unparking(类似被唤醒的意思)。该方法返回false。

经过这轮循环,队列模型变成下面状态

       +--------------------------+   prev           +------------------+
head | waitStatus = Node.SIGNAL | <---- node(tail) | currentThread |
+--------------------------+ +------------------+

因为shouldParkAfterFailedAcquire返回的是false,所以后面这个条件就不再看了。继续 for (;;)  中的循环。

如果state仍然大于0,再次进入到 shouldParkAfterFailedAcquire。

这次因为head 中的waitStatus 为 Node.SIGNAL ,所以 shouldParkAfterFailedAcquire 返回true。

这次就需要看parkAndCheckInterrupt 这个方法了。

 private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

ok,线程没有被中断,所以,返回false。继续 for (;;)  中的循环。

如果state 一直大于0,并且线程一直未被中断,那么就一直在这个循环中。也就是我们上篇文章说的裁判一直不愿意宣布比赛结束的情况。

那么什么情况下跳出循环呢?也就是什么情况下state 会 小于0呢? 下一篇文章 我将说明。

总结一下,await()  方法 其实就是初始化一个队列,将需要等待的线程(state > 0)加入一个队列中,并用waitStatus 标记后继结点的线程状态。

CountDownLatch 源码解析—— await()的更多相关文章

  1. CountDownLatch源码解析

    一.CountDownLatch介绍 CountDownLatch是在jdk1.5被引入的,它主要是通过一个计数器来实现的,当在初始化该类的构造函数时,会事先传入一个状态值,之后在执行await方法后 ...

  2. CountDownLatch 源码解析—— countDown()

    上一篇文章从源码层面说了一下CountDownLatch 中 await() 的原理.这篇文章说一下countDown() . public void countDown() { //CountDow ...

  3. Java并发包源码学习系列:同步组件CountDownLatch源码解析

    目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void c ...

  4. 死磕 java同步系列之CountDownLatch源码解析

  5. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  6. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  7. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  8. Java - "JUC" CountDownLatch源码分析

    Java多线程系列--“JUC锁”09之 CountDownLatch原理和示例 CountDownLatch简介 CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前 ...

  9. 【JUC源码解析】Exchanger

    简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...

随机推荐

  1. dijit.byId("grid") is undefined

    1.错误描述 TypeError:dijit.byId(...) is undefined     (68 out of range 3) 2.错误原因    var gridName = dijit ...

  2. 芝麻HTTP:一个采集系统的构建

    整个系统: 采集系统:

  3. cfDNA基本知识

    定义 Circulating free DNA or Cell free DNA (cfDNA):循环游离DNA或者细胞游离DNA,释放到血浆中的降解的DNA片段. https://en.wikipe ...

  4. 由js深拷贝引起的对内存空间的一些思考

    数据类型 js常用数据类型分为基本类型和引用类型 基本类型:null.undefined.数值型.字符串型.布尔型 引用类型:数组.对象 内存空间 var a = [1, 2, 3]; var b = ...

  5. DBdbvis数据库驱动连接问题

    今天使用数据库查询工具DBvis链接mysql数据库时, 发现执行如何sql语句, 都报如下错误: mysql驱动包的版本不对导致的.我的mysql版本是5.7 解决办法: 去mysql官网下载最新的 ...

  6. 【BZOJ5093】图的价值(第二类斯特林数,组合数学,NTT)

    [BZOJ5093]图的价值(第二类斯特林数,组合数学,NTT) 题面 BZOJ 题解 单独考虑每一个点的贡献: 因为不知道它连了几条边,所以枚举一下 \[\sum_{i=0}^{n-1}C_{n-1 ...

  7. 【Luogu1337】平衡点(模拟退火)

    [Luogu1337]平衡点(模拟退火) 题面 洛谷 题解 和BZOJ3680吊打XXX是一样的.. 但是数据很强呀.. 疯狂调参 各种WA... 很无奈呀.... #include<iostr ...

  8. 【BZOJ4199】【NOI2015】品酒大会(后缀数组)

    [BZOJ4199][NOI2015]品酒大会 题面 BZOJ Uoj 洛谷 题解 考虑最裸的暴力 枚举每次的长度 以及两个开始的位置 检查以下是否满足条件,如果可以直接更新答案 复杂度\(O(n^3 ...

  9. 【NOIP2009】【CJOJ1687】【洛谷1074】靶形数独

    题面 Description 小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低.但普通的数独对他们来说都过于简单了,于是他们向 Z博士请教,Z 博士拿出 ...

  10. [SDOI2013]费用流

    然而这是一道网络流... 如果满足Bob,使总费用最大: 设最大流的每条边流量(不是容量)为w[i],分配到每条边的费用为p[i],最大流量为wmax,p[i]的和为P 那么显然w[i] * p[i] ...