并发编程(8):CountDownLatch的基本使用与实现原理

1、CountDownLatch的作用:允许一个或多个线程等待其他线程完成操作。
      意思就是使用CountDownLatch来阻塞一条或多条线程,当CountDownLatch的计数被线程减到0后,就唤醒阻塞的一条或多条线程。

 

2、CountDownLatch的使用案例:

      案例一:使用一个计数为3的CountDownLatch 实例阻塞两条线程T1、T2,随机创建3条线程,每条线程去将计数-1,当3变成0的时候,就会唤醒阻塞的T1、T2线程。

public class CountDownLatchDemo {

    //创建一个计数为3的CountDownLatch实例
    public static final CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            try {
                //使用countDownLatch阻塞当前线程。
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("T1 wake........");
        },"T1").start();

        new Thread(()->{
            try {
                //使用countDownLatch阻塞当前线程。
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T2 wake........");
        },"T2").start();

        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //当前线程做计数-1操作,当3变成0的时候,阻塞的线程唤醒(可阻塞多个线程)。
                countDownLatch.countDown();
            }).start();
        }
    }
}

                   结果输出:

                           T1 wake........
                           T2 wake........

 

       使用案例二:使用一个计数为1的CountDownLatch 去阻塞10条线程,使用main线程去将计数-1,就会唤醒10条线程。

public class CountDownLatchDemo1 {

    public static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10;i++){
            new Thread(()->{
                try {
                    countDownLatch.await();
                    System.out.println(Thread.currentThread().getName()+"-----wake......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            },"T"+i).start();
        }

        Thread.sleep(1000L);
        countDownLatch.countDown();
    }
}

                   输出结果:

                             T0-----wake......
                             T3-----wake......
                             T9-----wake......
                             T2-----wake......
                             T6-----wake......
                             T5-----wake......
                             T1-----wake......
                             T7-----wake......
                             T4-----wake......
                             T8-----wake......

 

3、CountDownLatch的实现原理分析:

      根据使用的方式我们可以看出,CountDownLatch类的await()方法阻塞线程、countDown()方法会将计数-1。当计数为0的时候唤醒被阻塞的所有线程。

      单从CountDownLatch的表现出来的特性上来看,跟我们之前说的Condition特别类似,那么CountDownLatch的实现方式是使用Condition的等待队列来实现的吗?还有一种可能就是CountDownLatch也可能是使用AQS的同步队列来实现的,因为AQS也满足阻塞线程+唤醒线程的特性。

      AQS 还是 Condition?????????

      这个问题我们通过看源码的方式来确定一下:我们先看CountDownLatch的await()方法。

      3.1、CountDownLatch的类结构:通过类结果其实我们就能看出其实CountDownLatch的实现是AQS同步器。

public class CountDownLatch {

    //静态的内部同步类,实现了AQS
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        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;
            }
        }
    }

    //同步器
    private final Sync sync;
    
    //构造函数
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    //阻塞当前线程
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    //将计数-1
    public void countDown() {
        sync.releaseShared(1);
    }
  
    public long getCount() {
        return sync.getCount();
    }

    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

       3.2、CountDownLatch的构造函数:

    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }



    Sync(int count) {
        //设置AQS的state标记为CountDownLatch的计数
        setState(count);
    }

      3.3、CountDownLatch的await()方法实现:

    public void await() throws InterruptedException {
        //使用同步器获取申请一个队列位置,构建的node 模式是shared 模式的即共享模式,这个过程会先检查是否中断。
        sync.acquireSharedInterruptibly(1);
    }


    //接上面 sync.acquireSharedInterruptibly(1);  入参arg=1
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {

        //先检查是否中断,如果是直接抛出InterruptedException异常。
        if (Thread.interrupted())
            throw new InterruptedException();

        //尝试获取一个资源,这个理主要是判断AQS中的state(CountDownLatch的计数)是否为0 ,如果为0返回1 否则返回-1.
        if (tryAcquireShared(arg) < 0)

            //如果CountDownLatch的计数不为0,那就以共享可中断模式获取资源,
            doAcquireSharedInterruptibly(arg);
    }



    //接 doAcquireSharedInterruptibly(arg);
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {

        //构建一个链表的节点模式是SHARED,如果需要初始化队列,那就初始化。
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //获取AQS的head节点
                final Node p = node.predecessor();

                if (p == head) {
                    //如果是head节点,就判断CountDownLatch的计数是否为0
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {

                        //如果CountDownLatch的计数是0,那就以迭代的形式去逐步唤醒队列中的所有线程。
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }

                /*如果不是head节点,或者是head节点但是CountDownLatch的计数不等于0,那就阻塞当
                  前线程,并将节点的waitState改为-1 SIGNAL状态。线程会阻塞在此处,等待
                  CountDownLatch的计数变为0后唤醒。唤醒后此处区分于ReentrantLock,因为唤醒后
                  此处不会自我阻塞。而是直接抛出异常。
                */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

      3.4、上面跟踪到CountDownLatch.await()方法说道线程阻塞在parkAndCheckInterrupt()方法的地方等待被唤醒,接下来我们看看是如何唤醒的。我们来看看CountDownLatch.countDown()方法。

    public void countDown() {
        //使用同步来释放资源
        sync.releaseShared(1);
    }


    //接  sync.releaseShared(1); 入参arg=1
    public final boolean releaseShared(int arg) {

        //将CountDownLatch的计数-1(AQS的state),当等于0的时候返回true,否则返回false
        if (tryReleaseShared(arg)) {
 
            //如果CountDownLatch的计数=0 那就去释放阻塞的线程。
            doReleaseShared();
            return true;
        }
        return false;
    }




    private void doReleaseShared() {
        /*自旋来唤醒head节点的next节点,上面我们说了当等待的线程节点,只要有一个呗唤醒,就会以迭
          代的形式逐步唤醒后面的线程,也就是说前一个节点的线程唤醒后一个节点的线程。那么也就是 
          说,当某线程调用countDown()方法后发现计数为0 了,那么当前线程只会唤醒head节点的next节 
          点的线程,然后next节点的线程去唤醒下一个节点的线程,以次传递下去。

          因此在此方法中只会唤醒head节点的next节点中的线程就会结束自旋。
        */
        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;
        }
    }

      3.5、上面跟踪到了doReleaseShared的方法中会唤醒head节点的next的线程,那么阻塞的线程就会允许,继续自己的下一次循环,我们来分析一下被唤醒的线程的下一次循环:

 private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //head节点的next节点的线程 来获取前一个节点,肯定是head了,没有疑问。
                final Node p = node.predecessor();
                if (p == head) {
                    //去判断countDownLatch的计数是否为0,当然是0了,这是我们的前提条件嘛,没有疑问。
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {

                        //countDownLatch的计数为0了,去更换head节点,并唤醒下一个节点的线程,依次传递下去,直到将所有的线程唤醒位置。
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //通过最后一个调用countDown()的方法在此处唤醒了的head节点的next节点的线程,进入下一次循环。
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

         到此CountDownLatch的原理分析结束。

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值