上一篇文章从源码层面说了一下CountDownLatch 中 await() 的原理。这篇文章说一下countDown() 。

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

public final boolean releaseShared(int arg) { //AQS
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

protected boolean tryReleaseShared(int releases) { //CountDownLatch.Sync
// 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;
}
}

通过构造器 CountDownLatch end = new CountDownLatch(2);  state 被设置为2,所以c == 2,nextc = 2-1,

然后通过下面这个CAS操作将state设置为1。

  protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

此时nextc还不为0,返回false。一直等到countDown()  方法被调用两次,state == 0,nextc ==0,此时返回true。

进入doReleaseShared()方法。

doReleaseShared();

private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
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
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}

回顾一下此时的等待队列模型。

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

此时head 不为null,也不为tail,waitStatus == Node.SIGNAL,所以进入 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) 这个判断。

if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

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

这个CAS 操作将 state 设置为 0 ,也就是说此时Head 中的 waitStatus 是0.此时队列模型如下所示

       +----------------+   prev           +------------------+
head | waitStatus = 0 | <---- node(tail) | currentThread |
+----------------+ +------------------+

该方法返回true。进入unparkSuccessor(h);

unparkSuccessor(h);

private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

s 就是head的后继结点,也就是装有当前线程的结点。s != null ,并且s.waitStatus ==0 ,所以进入 LockSupport.unpark(s.thread);

 public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}

也就是unlock 被阻塞的线程。裁判被允许吹哨了!

countDown() 的原理就此就非常清晰了,

每执行一次countDown() 方法,state 就是减1,直到state == 0,则开始释放被阻塞在队列中的线程,根据前驱结点中waitStatus的状态,释放后续结点中的线程。

OK,回到上一篇文章的问题,什么时候跳出下面这个循环(await方法中的循环)

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,所以进入 setHeadAndPropagate 方法。

setHeadAndPropagate(node, r);

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}

private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}

这个方法将head 的后继结点变为head。该方法过后,又将node的next结点设置为null,模型变成下图

       prev                +---------+  next
null <---- node(tail/head) | null | ----> null
+---------+

也就是node head tail 什么的都被置为null,等待GC回收了,这个时候return,跳出了for循环,队列被清空。

下面演示一下整个过程

setHeadAndPropagate(node, r);

           +----------------+
head(tail) | waitStatus=0 |
| thread =null |
+----------------+

+----------------+ +----------------+
| waitStatus=0 | prev | waitStatus=0 |
head(tail) | thread =null | <---- node | currentThread |
+----------------+ +----------------+

+----------------+ +----------------+
| waitStatus=0 | prev | waitStatus=0 |
head | thread =null | <---- node(tail) | currentThread |
+----------------+ +----------------+

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

+----------------+ +----------------+
| waitStatus=0 | prev | waitStatus=0 |
head | thread =null | <---- node(tail) | currentThread |
+----------------+ +----------------+

+----------------+
prev | waitStatus=0 | next
null <---- node(tail/head) | null | ----> null
+----------------+

CountDownLatch 的核心就是一个阻塞线程队列,这是由链表构造而成的队列,里面包含thread 和 waitStatus,其中waitStatus说明了后继结点线程状态。

state 是一个非常重要的标志,构造时,设置为对应的n值,如果n != 0,阻塞队列将一直阻塞,除非中断线程。

每次调用countDown()  方法,就是将state-1,而调用await() 方法就是将调用该方法的线程加入到阻塞队列,直到state==0,才能释放线程。

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

  1. CountDownLatch源码解析

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

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

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

  3. CountDownLatch 源码解析—— await()

    上一篇文章说了一下CountDownLatch的使用方法.这篇文章就从源码层面说一下await() 的原理. 我们已经知道await 能够让当前线程处于阻塞状态,直到锁存器计数为零(或者线程中断). ...

  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. FFMPEG:压缩之H264编码(YUV420P->H264)

    720*576@25hz,550帧的yuv420p数据,编码时间13.3秒. void CTest0Dlg::OnButton5() { // TODO: Add your control notif ...

  2. Flex中TabNavigator隐藏和显示选项卡

    1.问题背景 遇到这样一个问题:有四个Tab选项卡,根据不同的参数隐藏和显示选项卡 2.实现实例 (1)隐藏"春季" protected function springClickH ...

  3. 芝麻HTTP:爬虫的基本原理

    我们可以把互联网比作一张大网,而爬虫(即网络爬虫)便是在网上爬行的蜘蛛.把网的节点比作一个个网页,爬虫爬到这就相当于访问了该页面,获取了其信息.可以把节点间的连线比作网页与网页之间的链接关系,这样蜘蛛 ...

  4. MFC与Webbrower交互(通过JS)

    最近修改老旧的MFC项目,用的网页做界面,和HTML交互采用的是COM方式,繁琐,丑陋又性能低,于是考虑利用js来进行界面交互,查了一天的资料,现在整理如下,供后来需要的人参考,虽然大概几乎不会有人用 ...

  5. cisco linksys ea3500 刷机 openwrt

    家中router改造成千兆华为A1,淘汰下来的cisco linksys ea3500  终于可以去刷机 openwrt,尽情折腾啦! 分享步骤: 准备文件 https://archive.openw ...

  6. View的放大->旋转->还原动画

    以UIButton为例,创建一个类,继承于UIButton /*页面的创建用storyboard*/ .h文件  @interface PTSRecommendButton : UIButton - ...

  7. [Luogu2057]善意的投票

    题目戳我 题目描述 幼儿园里有n个小朋友打算通过投票来决定睡不睡午觉.对他们来说,这个问题并不是很重要,于是他们决定发扬谦让精神.虽然每个人都有自己的主见,但是为了照顾一下自己朋友的想法,他们也可以投 ...

  8. Poj2749:Building roads

    题意 有 N 个牛栏,现在通过一条通道(s1,s2)要么连到s1,要么连到s2,把他们连起来,他们之间有一些约束关系,一些牛栏不能连在同一个点,一些牛栏必须连在同一个点,现在问有没有可能把他们都连好, ...

  9. Delphi的RzDbgrid改变某行的背景色

    本想改变符合条件的行的背景色,试了DbgridEh和原生的Dbgrid直接在DrawColumnCell事件中写重绘代码就好了,But在RzDbgrid就不起效果,查了好一会,百度了一大堆,都是千篇一 ...

  10. 谷歌chrome 插件(扩展)开发——进阶篇(c#本地程序和插件交互)下

    在上一篇中,我提出了总任务.接下来去实现. 获取网页内容等其它信息,这是content.js 擅长做的事情: chrome.extension.onMessage.addListener( funct ...