一、简介
1、CountDownLatch类是一个同步辅助装置,允许一个或多个线程去等待直到另外的线程完成了一组操作。
2、它通过count进行初始化,await方法会阻塞直到当前的count为0由于调用了countDown方法,之后所有的线程将被释放并且立即返回结果。count不能被重置,如果你想count可以重置,请使用CyclicBarrier。
3、CountDownLatch是一个通用的同步工具,可用于多种用途。CountDownLatch初始化使用count作为一个简单的可开可关的大门:所有的线程调用await方法等待在大门里,当一个线程调用了countDown方法后大门打开
二、源码分析
public class CountDownLatch {
//内部类Sync继承了AQS
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//构造方法中的count其实就是传给了AQS的state属性
Sync(int count) {
setState(count);
}
//得到的AQS的state属性值
int getCount() {
return getState();
}
//重写的AQS的tryAcquireShared,在共享模式的情况下获取锁
protected int tryAcquireShared(int acquires) {
//获取锁的前提是state为0,表示当前未被其他线程占有
return (getState() == 0) ? 1 : -1;
}
//重写的AQS的tryReleaseShared,在共享模式下释放锁
protected boolean tryReleaseShared(int releases) {
// 减count; 当count为0时唤醒
for (;;) {
int c = getState();
if (c == 0) //表示释放锁的前提是占有锁,也就是state的属性值大于0
return false;
int nextc = c-1; //state的值减1
if (compareAndSetState(c, nextc)) //利用CAS来改变state的值
return nextc == 0; //当state的值为0时返回true
}
}
}
private final Sync sync;
/**
* 通过count初始化一个CountDownLatch
*
*在线程调用await方法后如果想通过,必须执行count次countDown方法
* @如果count为负数,则抛出IllegalArgumentException
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
================================================================================================
两个核心方法,其实底层使用的AQS的方法(共享模式下)
/**
* 调用await方法会使当前的线程等待除非count的数值降为0或者中间抛出异常
*
* 如果当前的count为0,则该方法立即返回
*
*如果当前的count大于0,则当前线程因线程调度目的而被禁用,并且休眠,直到发生以下两种情况之一:
*1、调用countDown方法将count的值降为0
*2、其他的线程打断了当前线程,使用Thread.interrupted
*/
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
*减count的值,如果count的值为0了,则释放所有等待的线程
*
*如果当前的count值大于0,那么它是被减了。
*如果当前的count值等于0,那么所有等待的线程被唤醒接受线程的调度
*/
public void countDown() {
sync.releaseShared(1);
}
================================================================================================
/**
*返回count的值,用于调试或者测试
*/
public long getCount() {
return sync.getCount();
}
三、小练习-模拟王者荣耀单挑
public static void main(String[] args) throws Exception{
CountDownLatch cd = new CountDownLatch(3);
Thread beginGame = new Thread(new Runnable() {
@Override
public void run() {
try{
cd.await();
System.out.println("欢迎来到王者荣耀,敌军还有30秒到达战场============");
}catch(Exception e){
e.printStackTrace();
}
}
},"beginGame");
Thread player1 = new Thread(new Runnable() {
@Override
public void run() {
try{
System.out.println("玩家一以准备=======");
cd.countDown();
}catch(Exception e){
e.printStackTrace();
}
}
},"player1");
Thread player2 = new Thread(new Runnable() {
@Override
public void run() {
try{
System.out.println("玩家二以准备=======");
cd.countDown();
}catch(Exception e){
e.printStackTrace();
}
}
},"player2");
beginGame.start();
player1.start();
player2.start();
}
四、总结
1、请注意,只有在共享模式下才能使用CountDownLatch,因为只有在共享模式下,AQS的state属性的值才有可能大于1,才有后续的等待state的值为0,其他的线程才能被唤醒继续执行的可能,这里针对的多个线程等待!
2、CountDownLatch里面有两个核心方法:await和countDown。这里注意,await和平时学习的Condition中的await不一样,这里的await就是线程获取锁且必须在state值为0,也就是该资源未被占有的情况下,获取锁成功后state的值从0变为1,表示该线程持有了该资源的锁。countDown就是将state的属性值减1。这两个方法其实都是调用的AQS的方法执行的。
3、初始化传入的count表示的就是AQS的state属性的值,可以理解为持有该资源的线程数,其他的线程想要拿到这个资源,必须等到该资源的state的值变为0才能被唤醒去获取锁。也说明了如果该state的值要从N变为0,需要执行N次countDown方法
4、有点类似于线程通信机制的wait/notify。
5、CountDownLatch典型的用法是将一个程序分为n个互相独立的可解决任务,并创建值为n的CountDownLatch。当每一个任务完成时,都会在这个锁存器上调用countDown,等待问题被解决的任务调用这个锁存器的await,将他们自己拦住,直至锁存器计数结束。
6、个人理解:线程A和线程B争夺一个资源,且线程B需要在线程A之后才能执行,线程A首先拿到这个资源,然后线程A需要执行一个大任务,这个大任务可以分解为N个小任务,所以,创建一个N的count的CountDownLatch,所以,state的值就是N了,线程A每执行完一个小任务后,将调用CountDownLatch的countDown方法将state的值减1,直到最后,所有的小任务执行完毕,代表线程A的大任务也就执行完毕了,此时,该资源的state的值为0了,代表可以被其他线程争夺获取锁。线程B就可以调用countDown的await就能够获取该资源的锁,并将state的值变为1。