CountDownLatch原理剖析

发布时间:2024年01月11日

案例介绍

在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。

在CountDownLatch出现之前一般都使用线程的join()方法来实现这一点,但是join方法不够灵活,不能够满足不同场景的需要,所以JDK开发组提供了CountDownLatch这个类,我们前面介绍的例子使用CountDownLatch会更优雅。

使用CountDownLatch的代码如下:

在这里插入图片描述
输出结果如下。

在这里插入图片描述
在如上代码中,创建了一个CountDownLatch实例,因为有两个子线程所以构造函数的传参为2。

主线程调用countDownLatch.await()方法后会被阻塞。子线程执行完毕后调用countDownLatch.countDown()方法让countDownLatch内部的计数器减1,所有子线程执行完毕并调用countDown()方法后计数器会变为0,这时候主线程的await()方法才会返回。

其实上面的代码还不够优雅,在项目实践中一般都避免直接操作线程,而是使用ExecutorService线程池来管理。

使用ExecutorService时传递的参数是Runable或者Callable对象,这时候你没有办法直接调用这些线程的join()方法,这就需要选择使用CountDownLatch了。

将上面代码修改为如下:
在这里插入图片描述

实现原理探究

从CountDownLatch的名字就可以猜测其内部应该有个计数器,并且这个计数器是递减的。

下面就通过源码看看JDK开发组在何时初始化计数器,在何时递减计数器,当计数器变为0时做了什么操作,多个线程是如何通过计时器值实现同步的。

为了一览CountDownLatch的内部结构,我们先看它的类图。

在这里插入图片描述
从类图可以看出,CountDownLatch是使用AQS实现的。通过下面的构造函数,你会发现,实际上是把计数器的值赋给了AQS的状态变量state,也就是这里使用AQS的状态值来表示计数器值。

在这里插入图片描述

void await()方法

当线程调用CountDownLatch对象的await方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:当所有线程都调用了CountDownLatch对象的countDown方法后,也就是计数器的值为0时;其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException异常,然后返回。

下面看下在await()方法内部是如何调用AQS的方法的。

在这里插入图片描述
从以上代码可以看到,await()方法委托sync调用了AQS的acquireSharedInterruptibly方法,后者的代码如下:

在这里插入图片描述
由如上代码可知,该方法的特点是线程获取资源时可以被中断,并且获取的资源是共享资源。

acquireSharedInterruptibly首先判断当前线程是否已被中断,若是则抛出异常,否则调用sync实现的tryAcquireShared方法查看当前状态值(计数器值)是否为0,是则当前线程的await()方法直接返回,否则调用AQS的doAcquireSharedInterruptibly方法让当前线程阻塞。

另外可以看到,这里tryAcquireShared传递的arg参数没有被用到,调用tryAcquireShared的方法仅仅是为了检查当前状态值是不是为0,并没有调用CAS让当前状态值减1。

boolean await(long timeout,TimeUnit unit)方法

当线程调用了CountDownLatch对象的该方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:当所有线程都调用了CountDownLatch对象的countDown方法后,也就是计数器值为0时,这时候会返回true;设置的timeout时间到了,因为超时而返回false;其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程会抛出InterruptedException异常,然后返回。

在这里插入图片描述

void countDown()方法

线程调用该方法后,计数器的值递减,递减后如果计数器值为0则唤醒所有因调用await方法而被阻塞的线程,否则什么都不做。下面看下countDown()方法是如何调用AOS的方法的。

在这里插入图片描述
在这里插入图片描述
在如上代码中,releaseShared首先调用了sync实现的AQS的tryReleaseShared方法,其代码如下。

在这里插入图片描述
如上代码首先获取当前状态值(计数器值)。

代码(1)判断如果当前状态值为0则直接返回false,从而countDown()方法直接返回;否则执行代码(2)使用CAS将计数器值减1,CAS失败则循环重试,否则如果当前计数器值为0则返回true,返回true说明是最后一个线程调用的countdown方法,那么该线程除了让计数器值减1外,还需要唤醒因调用CountDownLatch的await方法而被阻塞的线程,具体是调用AQS的doReleaseShared方法来激活阻塞的线程。

这里代码(1)貌似是多余的,其实不然,之所以添加代码(1)是为了防止当计数器值为0后,其他线程又调用了countDown方法,如果没有代码(1),状态值就可能会变成负数。

long getCount()方法

获取当前计数器的值,也就是AQS的state的值,一般在测试时使用该方法。下面看下代码。

在这里插入图片描述

文章来源:https://blog.csdn.net/zhuyufan1986/article/details/135524671
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。