concurrent(五)同步辅助器CountDownLatch & 源码分析
参考文档:
https://blog.csdn.net/zxdfc/article/details/52752803
简介
CountDownLatch是一个同步辅助类。允许一个或多个线程等待其他线程完成操作。内部采用的公平锁和共享锁的机制实现
举个栗子
public class CountDownLatchTest { public static void main(String[] args) { CountDownLatch cd = new CountDownLatch(2); ExecutorService es = Executors.newFixedThreadPool(5); for (int i = 0; i < 2; i++) { es.execute(new MyThread1(cd)); } System.out.println("wait"); try { cd.await(); System.out.println("two thread run over"); } catch (InterruptedException e) { e.printStackTrace(); } es.shutdown(); } } class MyThread1 implements Runnable { CountDownLatch latch; public MyThread1(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { Thread.sleep(1000 * 3); latch.countDown(); System.out.println(Thread.currentThread().getName() + " over"); } catch (InterruptedException e) { e.printStackTrace(); } } }
源码分析
构造方法
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
函数列表
void await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断。PS:当count等于0的时候,再去调用await(),线程将不会阻塞,而是立即运行。后面可以通过源码分析得到
boolean await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
void countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程
long getCount() :获得计数的数量
await分析
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//判断是否发生中断 throw new InterruptedException(); if (tryAcquireShared(arg) < 0)//注意:-1表示还有线程未执行完毕,1表示等待的线程已经执行完毕 doAcquireSharedInterruptibly(arg); }
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state }
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);//就判断尝试获取锁 /* 这里要注意一下r的值就2种情况-1和1: 情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待 情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点 */ if (r >= 0) { //1表示等待的线程已经执行完毕 setHeadAndPropagate(node, r);//将当前节点设置头结点,如果还有资源则唤醒后继节点 p.next = null; //删除原来的头结点的引用,则原头结点可以gc掉 failed = false; return; } } //如果当前线程不是第二个节点,寻找安全点,进入waiting状态,等着被unpark()或interrupt() if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) //调用park()使线程进入waiting状态,如果被唤醒,查看自己是不是被中断的 throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; // 记录头结点 setHead(node);//设置node为头结点 /* *从上面传递过来 propagate = 1; *一定会进入下面的判断 */ if (propagate > 0 || h == null || h.waitStatus < 0) { Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared if (s == null || s.isShared()) doReleaseShared();//唤醒后继节点 } }
//唤醒后继节点 private void doReleaseShared() { for (;;) { Node h = head;//获得头结点 if (h != null && h != tail) { int ws = h.waitStatus;//获取头结点的状态默认值为0 if (ws == Node.SIGNAL) {//如果等于SIGNAL唤醒状态 //将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL) if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); }//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } //如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break if (h == head) // loop if head changed break; } }
countDown分析
public void countDown() { sync.releaseShared(1);//每次释放一个 }
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {//尝试释放共享锁 doReleaseShared();//释放共享锁,上面有泛型不在重复 //由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待, return true; } return false; }
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))//比较并设置state return nextc == 0; //等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理 } }