CountDownLatch

1.什么是CountDownLatch?

建议先看这篇文章,里面有部分方法之前的文章里面写过,就没有重复写了:从源码去分析J.U.C核心之AQS

CountDownLatch是一个工具类,它允许一个或者多个线程一直等待,直到其他线程的操作执行完毕再执行,从命名可以解读到CountDown是倒数的意思,类使用我们倒计时的概念。

我们来看如下一段代码:注意到一共有两个线程,线程里面分别去执行countDown,CountDownLatch我们传入的值是3。这样永远看不到输出“线程执行完毕”这句话。
当我们在代码中再加入一个线程去执行,就能看到输出了“线程执行完毕”。那么我们可以发现这里类似于一个倒计时,每次调用countDown就会数一次,当我们预先设定的值数完之后,就会执行await之后的代码。

	public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(()->{
           countDownLatch.countDown();
        });
        new Thread(()->{
            countDownLatch.countDown();
        });
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程执行完毕");
    }

我们可以从下面的流程图来对它进一步了解

在这里插入图片描述

2.源码分析

首先,我们看下类的关系图:

在这里插入图片描述

CountDownLatch的内部定义了一个抽象类Sync继承了AbstractQueuedSynchronizer。这里主要用到了AQS里面一个共享锁的功能。

我们看下它的一个初始化构造函数,

在这里插入图片描述

这里面创建 了一个Sync的实例

在这里插入图片描述

方法里面又把传进来得到count值给了AQS的state,我们知道state为0表示没有锁的状态,大于0表示没有锁的状态。可以参考我的另外一篇文章从源码去分析J.U.C核心之AQS。每次调用countDown方法其实是对state进行递减,当值为0时,await方法才会被唤醒。接下来我们看看await里面做了什么。

2.1 await方法

在这里插入图片描述

可以看到这个方法里面其实是调用了Sync的acquireSharedInterruptibly方法,这里是去获得一个共享锁的过程。

在这里插入图片描述
可以看到这里有一个tryAccquireShared方法,需要判断它的返回值是否小于0

在这里插入图片描述

显然,只有当state值为0时,才会返回1,什么时候才会为0?? countDown方法执行了设置的次数之后才会为0。也就是说,当我们处于阻塞时,这里肯定是返回的-1,那么此时会进入doAcquireSharedInterruptibly方法。

在这里插入图片描述

可以看到这里通过addWaiter定义了一个Node节点,addWaiter在另一篇文章中有介绍,它是封装了一个Node节点,并加到AQS队列里面。通过自旋,先拿到当前节点的上一个节点,如果上个节点是头节点,这里会尝试去获得state的状态值,此时因为countDown方法还未执行到指定次数,这里返回的是-1。这里会调用shouldParkAfterFailedAcquire方法,把它的前置节点ws修改成-1,并将当前节点挂起,变为阻塞状态。

2.2 countDown方法

这里会调用Sync的releaseShared方法去修改state的值,传的参数是1
在这里插入图片描述

在这里插入图片描述

这里通过自旋的方式去获得state的值,如果等于0,就返回false,否则就会用之前的state值去减1。

在这里插入图片描述

当最后一次执行该方法时,nextc就等于0了,此时通过compareAndSetState方法将state值改为0,并返回true。从上面可以看出,当返回true时,会继续执行doReleaseShared方法。这里按照我们的猜测,它应该回去进行一个唤醒的操作。我们继续看下代码。

在这里插入图片描述
首先,拿到了头节点,进行了判断,显然,此时的头节点是不等于空的,并且也不等于尾结点。此时去获取它的一个waitState值,这个值在shouldParkAfterFailedAcquire方法中已经修改成了-1,也就是Node.SIGNAL。然后再尝试把这个值更新成0,如果返回false就跳出 当前循环,如果为true就唤醒此时的头节点的next节点

在这里插入图片描述

可以看到,当前循环的结束条件为if (h == head) 这里可能会有疑问,因为最开始通过Node h = head;把head节点赋值给了h,那么,这个条件不是肯定成立的吗?这里我们看下第一个if条件。if (h != null && h != tail) { ,那么,意味着,我们能执行到最后的这个if条件,肯定是head为空或者head==tail,为空肯定是不可能的,所以,这里表示的所有的节点都已经被成功唤醒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值