一、CountDownlatch是什么?
JUC包中的CountDownLatch类实现线程间的通信,可以使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
在日常开发中经常会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在CountDownLatch出现之前一般都使用线程的join()方法来实现,但是join方法不够灵活,不能够满足不同场景的需要,所以JDK提供了CountDownLatch这个类,使用CountDownLatch会更优雅。
示例如下:
public class JoinCountDownLatch {
// 创建一个CountDownLatch 实例
private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("child threadOne over!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println("child threadTwo over!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
}
});
threadOne.start();
threadTwo.start();
System.out.println("wait all child thread over !");
countDownLatch.await();
System.out.println("all child thread over!");
}
}
总结下CountDownLatch与join()方法的区别。一个区别是调用一个子线程的 join() 方法后,该线程会一直被阻塞直到子线程运行完毕,而 CountDownLatch 则使用计数器来允许子线程运行完毕或者在运行中递减计数,计数器为零时就可以继续向下执行,也就是CountDownLatch可以在子线程运行的任何时候让await方法返回而不一定必须等到线程结束。另外,使用线程池来管理线程时一般都是直接添加Runable到线程池,这时候就没有办法再调用线程的join方法了,就是说cacountDownLatch相比join方法让我们对线程同步有更灵活的控制。
类图:
从类图可以看出,CountDownLatch是使用AQS实现的。通过下面的构造函数,你会
发现,实际上是把计数器的值赋给了AQS的状态变量state,也就是这里使用AQS的状态
值来表示计数器值。
二、源码分析
CountDownLatch本身是基于共享锁实现的。
CountDownLatch主要是通过AQS的共享锁机制实现的,因此它的核心属性只有一个sync,它继承自AQS,同时覆写了tryAcquireShared和tryReleaseShared,以完成具体的实现共享锁的获取与释放的逻辑。
构造器
在构造函数中,我们就是简单传入了一个不小于0的任务数,由上面Sync的构造函数可知,这个任务数就是AQS的state的初始值。
CountDownLatch最核心的方法只有两个,一个是countDown方法,每调用一次,就会将当前的count减一,当count值为0时,就会唤醒所有等待中的线程;另一个是await方法,它有两种形式,一种是阻塞式,一种是带超时机制sss的形式,该方法用于将当前等待“门闩(shuan)”开启的线程挂起,直到count值为0,这一点很类似于条件队列,相当于等待的条件就是count值为0,然而其底层的实现并不是用条件队列,而是共享锁。
2.1 countDown()
前面说过,countDown()方法的目的就是将count值减一,并且在count值为0时,唤醒所有等待的线程,它内部调用的其实是释放共享锁的操作:
该方法由AQS实现,但是tryReleaseShared方法由Sync类自己实现。
该方法的实现很简单,就是获取当前的state值,如果已经为0了,直接返回false;否则通过CAS操作将state值减一,之后返回的是nextc == 0,由此可见,该方法只有在count值原来不为0,但是调用后变为0时,才会返回true,否则返回false,并且也可以看出,该方法在返回true之后,后面如果再次调用,还是会返回false。也就是说,调用该方法只有一种情况会返回true,那就是state值从大于0变为0值时,这时也是所有在门闩(shuan)前的任务都完成了。
2.2 await()
与Condition的await()方法的语义相同,该方法是阻塞式地等待,并且是响应中断的,只不过它不是在等待signal操作,而是在等待count值为0:
从以上代码可以看到,await() 方法委托sync调用了AQS的acquireSharedInterruptibly方法,后者的代码如下:
我们来回忆一下独占模式下对应的方法:
我们先来看看sync子类对于tryAcquireShared的实现:
所谓的获取共享锁,事实上并不是什么抢锁的行为,没有任何CAS操作,它就是判断当前的state值是不是0,是就返回1,不是就返回-1。
前面我们提到过tryAcquireShared返回值的含义:
• 如果该值小于0,则代表当前线程获取共享锁失败。
• 如果该值大于0,则代表当前线程获取共享锁成功,并且接下来其他线程尝试获取共享锁的行为很可能成功。
• 如果该值等于0,则代表当前线程获取共享锁成功,但是接下来其他线程尝试获取共享锁的行为会失败。
所以,当该方法的返回值不小于0时,就说明抢锁成功,可以直接退出了,所对应的就是count值已经为0,所有等待的事件都满足了。否则,我们调用**doAcquireSharedInterruptibly(arg)**将当前线程封装成Node,丢到sync queue中去阻塞等待。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/77200.html