zoukankan      html  css  js  c++  java
  • 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(),来清理  
                    }  
                }  
  • 相关阅读:
    JS魔法堂:阻止元素被选中
    JS魔法堂之实战:纯前端的图片预览
    CentOS6.5菜鸟之旅:纯转载Linux目录结构
    Vim杂记:Sublime的配色方案
    Vim杂记:markdown插件
    CentOS6.5菜鸟之旅:中文编辑器忍痛放弃Sublime
    JS魔法堂:Data URI Scheme介绍
    CentOS6.5菜鸟之旅:安装ATI显卡驱动
    JS魔法堂:获取当前脚本文件的绝对路径
    腊八蒜
  • 原文地址:https://www.cnblogs.com/amei0/p/9018609.html
Copyright © 2011-2022 走看看