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();
        }
    }
}
View Code

源码分析

构造方法

 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(),来清理  
                }  
            }  

 

posted @ 2018-05-10 11:08  情歌z  阅读(202)  评论(0编辑  收藏  举报