首先CountDownLatch的作用在这里就不多说了,就是一个闭锁,其实现基于AQS, 想要理解它的实现原理就请有耐心的看完
本篇博文,看了之后基本就掌握的七七八八了,下面我们就来分析吧
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
首先从构造函数出发 初始化状态变量 ,其中sync是一个AQS的子类,构造函数如下
Sync(int count) { setState(count); }
设置状态变量state,其中state是个volatile 用于保证可见性
接着我们看await函数 ,
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
这里调用的是其内部类sync的函数,具体实现如下
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
首先判断是否被中断,中断就抛出异常,的话与tryAcquireShared(arg)的返回值相比较,具体实现如下
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
首先明白state状态变量,state的值代表着待达到条件的线程数,比如初始化为五,表示待达到条件的线程数为5,每次调用countDown()函数都会减一,所以当没有达到条件也就是state不等于0将会返回-1,进入if语句 ,接着往下看 这方法看起来很长
但不要慌我们慢慢看 当然首先你要明白,线程进入await方法阻塞后,会用一个一个的节点将线程串起来 等达到条件后再一个一个的唤醒,该链表是一个双向链表
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); //该函数 用于将当前线程相关的节点将入链表尾部 boolean failed = true; try { for (;;) { //将入无限for循环 final Node p = node.predecessor(); //获得它的前节点 if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { //唯一的退出条件,也就是await()方法返回的条件很重要!! setHeadAndPropagate(node, r); //该方法很关键具体下面分析 p.next = null; // help GC failed = false; return; //到这里返回 } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())// 先知道线程由该函数来阻塞的的 throw new InterruptedException(); } } finally { if (failed) //如果失败或出现异常,失败 取消该节点,以便唤醒后续节点 cancelAcquire(node); } }
我们先对addWaiter进行具体的剖析,看看线程如何变成节点被添加进去的 实现如下
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //首先new一个节点,该节点维持一个线程引用 // Try the fast path of enq; backup to full enq on failure Node pred = tail; //获取tail节点,tail是volatile型的 if (pred != null) { //不为空 node.prev = pred; if (compareAndSetTail(pred, node)) { //利用CAS设置,允许失败,后面有补救措施 pred.next = node; return node; } } enq(node); //设置失败,表明是第一个创建节点,或者是已经被别的线程修改过了会进入这里 return node; }
其实enq也没什么玄机,熟悉原子类实现原理的肯定可以看懂下面的操作
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; } } } }
好了到这里addWaiter就分析完成了,我们继续下面的分析工作,添加完节点之后,没有达到天剑就会进入这个判断
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
让我们首先看看shouldParkAfterFailedAcquire(p, node)方法 首先明白p是node的前节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; //获取前节点的状态 if (ws == Node.SIGNAL) //表明前节点可以运行 /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { //如果前节点状态大于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); //等于0进入这里 } return false; 只有前节点状态为NodeSIGNAL才返回真 }
我们对shouldParkAfterFailedAcquire来进行一个整体的概述,首先应该明白节点的状态,节点的状态是为了表明当前线程的良好度,如果当前线程被打断了,在唤醒的过程中是不是应该忽略该线程,这个状态标志就是用来做这个的具体有如下几种
/** waitStatus value to indicate thread has cancelled */ 线程已经被取消 static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ 线程需要去被唤醒 static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ 线程正在唤醒等待条件 static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should //线程的共享锁应该被无条件传播 * unconditionally propagate */ static final int PROPAGATE = -3;
目前你只需知道大于0时表明该线程已近被取消,已近是无效节点,不应该被唤醒,注意:初始化链头节点时头节点状态值为0。
shouldParkAfterFailedAcquire是位于无限for循环内的,这一点需要注意一般每个节点都会经历两次循环后然后被阻塞。建议读者试着走一遍,以加深理解 ,当该函数返货true时 线程调用parkAndCheckInterrupt这个阻塞自身。到这里基本每个调用await函数都阻塞在这里 (很关键哦,应为下次唤醒,从这里开始执行哦)
接着让我们来看看countDown这个函数的玄机吧,应为线程就是通过这个来函数来触发唤醒条件的
public void countDown() { sync.releaseShared(1); }
该函数也是委托其内部类完成,具体实现如下 这里arg为1 哦
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
看一下判断条件tryReleaseShared函数 protected boolean tryReleaseShared(int releases) { // 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; //nextc 为零时才返回真 } }
也就是说当state减1后为0时才会返回为真 执行后面的唤醒条件,否则都一概忽略,假设达到唤醒条件 具体来看如何唤醒 ,函数如下
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) { //头结点的状态为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; //这里是否有疑问明明都有这个 Node h = head为啥还要在判断一次?多次一举别着急后面有原因 } }
//这里执行唤醒添加
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); //唤醒线程 }
对于这个欢迎操作很好理解的,首先取该节点的后节点就行唤醒,如果后节点已被取消,则从最后一个开始往前找,找一个满足添加的节点进行唤醒
有人肯能会有疑问,要是如果有多个节点只在这进行一次唤醒工作吗?难道只唤醒一个线程就可以了?哈哈别急还记得线程是在哪阻塞的吗 让我们回来前面去看线程被阻塞的地方 (忘记了可以往前看看)
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); } }
关键来了,线程在这里被阻塞,唤醒后继续执行,由于满足条件,state的状态值为0,函数返回值为1 ,大于0会进入其中我们继续往下看 这一小段
setHeadAndPropagate(node, r); //关键就在这个函数哦 p.next = null; // help GC failed = false; return;
具体实现如下
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // Record old head for check below setHead(node); //这里重新设置头节点 (已上面 第一次释放锁 h== head 的重复判断相对应) /* * Try to signal next queued node if: * Propagation was indicated by caller, * or was recorded (as h.waitStatus) 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) { Node s = node.next; if (s == null || s.isShared()) 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; //明白这里为什么要加一次判断了吧!!!,被唤醒的线程会在执行该函数 } } 现在明白其唤醒机制了吧 先唤醒一个线程(第一个阻塞的线程) 然后被唤醒的线程又会执行到这里唤醒线程,如此重复下去 最终所有线程都会被唤醒,其实这也是AQS共享锁的唤醒原理,自此完成了对countDownLatch阻塞和唤醒原理的基本分析