CountDownLatch源码解析之countDown()

 更新时间:2018年04月01日 15:10:44   作者:猫咪大王  
这篇文章主要为大家详细解析了CountDownLatch源码之countDown方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

CountDownLatch 源码解析—— countDown()

上一篇文章从源码层面说了一下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,才能释放线程。

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java基础--反射机制

    Java基础--反射机制

    本文主要介绍了Java反射机制的相关知识。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-03-03
  • SpringBoot实现图片识别文字的四种方式小结

    SpringBoot实现图片识别文字的四种方式小结

    本文主要介绍了SpringBoot实现图片识别文字的四种方式,包括Tess4J,百度智能云,阿里云,腾讯云这四种,具有一定的参考价值,感兴趣的可以了解一下
    2024-02-02
  • JAVA中的延迟队列DelayQueue应用解析

    JAVA中的延迟队列DelayQueue应用解析

    这篇文章主要介绍了JAVA中的延迟队列DelayQueue应用解析,DelayQueue是一个根据元素的到期时间来排序的队列,而并非是一般的队列那样先进先出,最快过期的元素排在队首,越晚到期的元素排得越后,需要的朋友可以参考下
    2023-12-12
  • 使用Java生成jpg与压缩图片为jpg文件的代码示例

    使用Java生成jpg与压缩图片为jpg文件的代码示例

    这篇文章主要介绍了使用Java生成jpg与压缩图片为jpg文件的代码示例,都是对com.sun.image.codec.jpeg这个包的应用,需要的朋友可以参考下
    2015-11-11
  • 解决eclipse上传svn忽略target文件夹的坑

    解决eclipse上传svn忽略target文件夹的坑

    这篇文章主要介绍了解决eclipse上传svn忽略target文件夹的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • elasticsearch元数据构建metadata及routing类源码分析

    elasticsearch元数据构建metadata及routing类源码分析

    这篇文章主要为大家介绍了elasticsearch元数据构建metadata routing类内部源码分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-04-04
  • Java8新特性Stream流详解

    Java8新特性Stream流详解

    Java8 Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合进行链状流式的操作,本文就将带着你如何使用 Java 8 不同类型的 Stream 操作,同时还将了解流的处理顺序,以及不同顺序的流操作是如何影响运行时性能的
    2023-07-07
  • java常见的字符串拼接方式总结

    java常见的字符串拼接方式总结

    这篇文章主要为大家详细介绍了java中常见的字符串拼接方式,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-09-09
  • java爬虫Jsoup主要类及功能使用详解

    java爬虫Jsoup主要类及功能使用详解

    这篇文章主要为大家介绍了java爬虫Jsoup主要类及功能使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • 通过实例了解JavaBean开发及使用过程解析

    通过实例了解JavaBean开发及使用过程解析

    这篇文章主要介绍了通过实例了解JavaBean开发及使用过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08

最新评论