CountDownLatch,它可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。CountDownLatch的两个核心方法:调用await方法阻塞一个或多个线程;调用countDown方法,执行一组事件,每调用一次对“资源”数减1,当剩余“资源”数为0时,被阻塞的一个或多个线程同时被唤醒。这其实就是AQS的共享方式实现,在分析CountDownLatch实现原理之前,先来简单看看CountDownLatch的使用。
这有点类似游戏里组队玩游戏,假设游戏需要10个人,每个人加入游戏时都会被阻塞在开始界面,只有等人满后才能点击开始游戏。模拟代码如下:
- /**
- * Created by gantianxing on 2018/1/3.
- */
- public class CountDownLatchTest {
- public static void main(String[] args) throws InterruptedException {
- ExecutorService executorService = Executors.newFixedThreadPool(10);
- CountDownLatch playersCounter = new CountDownLatch(10);
- for (int i=0;i<10;i++){
- executorService.submit(new Thread(new Player(playersCounter,i+"")));
- }
- System.out.println("等待玩家加入游戏");
- //这里只模拟了一个线程阻塞,可以多线程调用await()阻塞
- playersCounter.await();
- System.out.println("游戏开始");
- executorService.shutdown();//关闭线程池
- System.out.println("游戏结束");
- }
- }
- //多线程操作线程不安全容器 ThreadSafe.datas
- class Player implements Runnable{
- private CountDownLatch playersCounter;
- private String name;
- public Player(CountDownLatch playersCounter,String name) {
- this.playersCounter = playersCounter;
- this.name = name;
- }
- @Override
- public void run() {
- System.out.println("玩家:"+name+"加入游戏");
- playersCounter.countDown();//对剩余的游戏坑位减1
- }
- }
执行上述main方法,可以发现只有等10个玩家都准备就绪后,游戏才能开始。可以发现使用CountDownLatch很简单,但我们不能仅仅停留在如何使用上,还应该更进一步了解其内部原理,以便再必要的时候创建自己的同步器。
CountDownLatch实现原理解析
在已经全面理解了AQS的前提下(见前一篇文章),再来看CountDownLatch内部实现,你会觉得非常简单。其内部核心实现就是,定义了一个实现了AQS的内部类Sync,AQS的公共资源状态字段state的值,就是初始化CountDownLatch(int count)的count值;调用CountDownLatch的await方法,会判断state的值是否变为0,如果不为0就阻塞该线程;调用CountDownLatch的countDown方法,没调用一次会对state减1,直到为0时,唤醒所有被阻塞的线程。
首先来看下内部类Sync的实现,AQS预留给子类实现的方法分为两类:排它和共享,排它 获取和释放方法分别为tryAcquir和tryRelease;共享 获取和释放方法分别为tryAcquireShared和tryReleaseShared。如果把CountDownLatch理解为锁的话,它属于共享锁的一种,因为所有阻塞的线程共享同一个开关,所以在CountDownLatch中Sync对AQS的实现,应该是共享实现,也就是说实现了tryAcquireShared和tryReleaseShared方法。我们来看下源码:
- private static final class Sync extends AbstractQueuedSynchronizer {
- private static final long serialVersionUID = 4982264981922014374L;
- Sync(int count) {
- setState(count);//设置AQS的队列状态state字段
- }
- int getCount() {
- return getState();
- }
- protected int tryAcquireShared(int acquires) {
- //判断AQS的队列状态state值是否变为0,如果不为0,阻塞该线程
- return (getState() == 0) ? 1 : -1;
- }
- protected boolean tryReleaseShared(int releases) {
- // Decrement count; signal when transition to zero
- for (;;) {
- int c = getState();
- if (c == 0)
- return false;
- //尝试对state字段减1
- int nextc = c-1;
- if (compareAndSetState(c, nextc))//CAS原子修改state值
- return nextc == 0;
- }
- }
- }
主要的方法就是构造方法Sync(int count)、获取资源方法tryAcquireShared(int acquires)、释放资源方法tryReleaseShared(int releases)。这三个方法分别会被CountDownLatch调用:
CountDownLatch构造方法
CountDownLatch的构造方法会调用Sync的构造方法Sync(int count),为AQS的state赋值;
- public CountDownLatch(int count) {
- if (count < 0) throw new IllegalArgumentException("count < 0");
- this.sync = new Sync(count);// 调用Sync的构造方法Sync(int count)
- }
CountDownLatch的await方法
CountDownLatch的await方法会调用Sync的tryAcquireShared方法,尝试获取锁,如果获取不到就阻塞;
- public void await() throws InterruptedException {
- //AQS的acquireSharedInterruptibly方法内部会调用tryAcquireShared方法
- sync.acquireSharedInterruptibly(1);
- }
CountDownLatch的countDown方法
CountDownLatch的countDown方法会调用Sync的tryReleaseShared方法释放资源,当state值变为0时,唤醒所有被阻塞的线程。
- public void countDown() {
- sync.releaseShared(1);//没调用一次,就会对AQS的state字段减1
- }
另外CountDownLatch的await方法还有延迟版本:
- public boolean await(long timeout, TimeUnit unit)
- throws InterruptedException {
- return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
- }
对应CountDownLatch的两个await方法,也许你已经注意到了 它们都会抛出InterruptedException异常,说明CountDownLatch实现的闭锁是可中断锁,调用线程的interrupt方法,可以中断阻塞的线程。
总结
简单的总结CountDownLatch闭锁就是:它实现了一个“共享锁”,可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。其核心实现就是基于AQS,另外CountDownLatch闭锁是“可中断锁”。
http://moon-walker.iteye.com/blog/2406502