上一篇文章从源码层面说了一下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. java.lang.NoClassDefFoundError:org/hamcrest/SelfDescribing

    1.错误描述 java.lang.NoClassDefFoundError:org/hamcrest/SelfDescribing 2.错误原因 将junit-4.11.jar导入到lib目录中,出现 ...

  2. IOS开发之XCode学习007:UIWindow对象

    此文学习来源为:http://study.163.com/course/introduction/1002858003.htm #import "AppDelegate.h" @i ...

  3. Vue 非父子组件通信方案

    Vue 非父子组件通信方案 概述 在 Vue 中模块间的通信很普遍 如果是单纯的父子组件间传递信息,父组件可以使用 props 将数据向下传递到子组件,而在子组件中可以使用 events (父组件需要 ...

  4. Http头介绍:Expires,Cache-Control,Last-Modified,ETag

    缓存分很多种:服务器缓存,第三方缓存,浏览器缓存等.其中浏览器缓存是代价最小的,因为浏览器缓存依赖的是客户 端,而几乎不耗费服务器端的资源. 让浏览器做缓存需要给浏览器发送指定的Http头,告诉浏览器 ...

  5. 【CJOJ P2226】[省常中2011S4] 圣诞节

    Description 圣诞节到了,FireDancer准备做一棵大圣诞树.下图为圣诞树的一个简单结构. 这棵树被表示成一组被编号的结点和一些边的集合.结点从1到n编号.树的根永远是1.每个结点都有一 ...

  6. 打造MacOS版“XShell”

    1.背景 XShell作为一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows 平台的TELNET 协议.作为server端开发,几乎是必备工具了. 很多刚 ...

  7. MySQL配置文件

    [mysqld]datadir=/usr/local//mysql/data    #数据存放位置socket=/var/lib/mysql/mysql.sockuser=mysqllower_cas ...

  8. 同一台电脑同时装Oracle客户端和服务端

    1.如果之前安装过Oracle,Win+R输入Services.msc,关掉以Oracle开头的服务(卸载Oracle服务端和客户端步骤一样,见另外一篇帖子) 2.Win+R输入regedit打开注册 ...

  9. angular路由模块(二)

    上一章写的是如何创建一个简单的路由,这一样我们来看看如何创建一个路由模块.angular的思想就是(模块,组件,子组件.....). 我们在src/app目录下创建一个跟路由模块app-routing ...

  10. 在Debian系列Linux系统Ubuntu上安装配置yum的试验

    用习惯了Red Hat系统的都知道我们习惯于三种安装方式:一种是rpm包的方式安装,一种就是tar包的方式来安装,还有一种方式就是yum源的安装. 首先rpm包的用法,我们一般是在Red Hat光驱里 ...