前面相继学习了ReentrantLock
和Semaphore
,都是基于AQS来实现的,这次学习的CountDownLatch
同样也是基于AQS实现的。
CountDownLatch
可以理解是一个倒计时计数器,是用于让一个或多个线程等待在其他线程的操作执行完之后再执行的同步辅助。值得注意的是。这里的倒计时计数是一次性的,计数无法重置,即调用countDown()
方法来计数,await()
方法等待阻塞直到计数减到零,到达零之后释放所有等待的线程,后续任何的await()
调用都是立即返回。
talk is cheap, show me code!还是从一个例子开始入手:
1 | public class CountDownLatchTest { |
运行结果:
1 | 正在攒钱买房... |
栗子就像社会一样现实,想要结婚(执行人生的主线程任务),就得先完成攒钱买车买房(这些子线程任务)。
首先初始化CountDownLatch
,传参是2,即定义为两个待完成目标:
1 | public CountDownLatch(int count) { |
构造方法中即初始化内部类Sync
(同ReentrantLock
、Semaphore
一样,都是AQS的实现类),Sync
的构造方法中调用AQS的setState()
方法,设置初始同步状态state,也可以理解为需要等待的目标数。
在执行的子线程中调用countDown()
方法来倒计时:
1 | public void countDown() { |
这里其实类似其他类的release()
方法,在Sync
的tryReleaseShared()
中,自旋,获取同步状态值state,如果state等于0,说明已经倒计时到0了,没有需要等待完成的目标了,返回false;如果不为0,则减一,再通过CAS修改state的值,如果修改成功,再判断减一之后的值是否等于0。这里如果返回true,才会调用AQS的doReleaseShared()
唤醒在等待的线程。
那么在等待的线程是如何进入等待的呢,栗子中的结婚主线程是调用await()
等待阻塞的:
1 | public void await() throws InterruptedException { |
这里倒像是前面学习的那些类的获取锁,通过getState()
获取同步状态state,如果等于0,则返回1,说明没有需要等待完成的目标了,可以直接执行当前线程;如果不等于0,返回-1,说明还有需要等待完成的目标,则调用AQS的doAcquireSharedInterruptibly()
方法将当前线程插入到等待队列队尾进行阻塞等待。
开头说到
CountDownLatch
计数是一次性的,看到这里也应该有了答案,因为await()
是需要判断当前同步状态state的值的,而countDown()
将state不断地减一,并没有其他方法来将state值重置,所以一旦state值到达了0,那么后续其他调用await()
等待的线程都会直接执行。
OK,CountDownLatch
类的实现比较简单,基本上就看完了,类注释中提到,针对需要重置计数,可以考虑使用CyclicBarrier
,那么下一目标就是学习CyclicBarrier
了。