Java闭锁—CountDownLatch

CountDownLatch,它可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。CountDownLatch的两个核心方法:调用await方法阻塞一个或多个线程;调用countDown方法,执行一组事件,每调用一次对“资源”数减1,当剩余“资源”数为0时,被阻塞的一个或多个线程同时被唤醒。这其实就是AQS的共享方式实现,在分析CountDownLatch实现原理之前,先来简单看看CountDownLatch的使用。

 

这有点类似游戏里组队玩游戏,假设游戏需要10个人,每个人加入游戏时都会被阻塞在开始界面,只有等人满后才能点击开始游戏。模拟代码如下:

Java代码   收藏代码
  1.    
  2. /** 
  3.  * Created by gantianxing on 2018/1/3. 
  4.  */  
  5. public class CountDownLatchTest {  
  6.    
  7.     public static void main(String[] args) throws InterruptedException {  
  8.         ExecutorService executorService = Executors.newFixedThreadPool(10);  
  9.         CountDownLatch playersCounter = new CountDownLatch(10);  
  10.         for (int i=0;i<10;i++){  
  11.             executorService.submit(new Thread(new Player(playersCounter,i+"")));  
  12.         }  
  13.    
  14.         System.out.println("等待玩家加入游戏");  
  15.         //这里只模拟了一个线程阻塞,可以多线程调用await()阻塞  
  16.         playersCounter.await();  
  17.         System.out.println("游戏开始");  
  18.    
  19.         executorService.shutdown();//关闭线程池  
  20.         System.out.println("游戏结束");  
  21.     }  
  22. }  
  23.    
  24. //多线程操作线程不安全容器 ThreadSafe.datas  
  25. class Player implements Runnable{  
  26.     private CountDownLatch playersCounter;  
  27.     private String name;  
  28.    
  29.     public Player(CountDownLatch playersCounter,String name) {  
  30.         this.playersCounter = playersCounter;  
  31.         this.name = name;  
  32.     }  
  33.    
  34.     @Override  
  35.     public void run() {  
  36.         System.out.println("玩家:"+name+"加入游戏");  
  37.         playersCounter.countDown();//对剩余的游戏坑位减1  
  38.     }  
  39. }  
  40.    

 

执行上述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方法。我们来看下源码:

Java代码   收藏代码
  1. private static final class Sync extends AbstractQueuedSynchronizer {  
  2.     private static final long serialVersionUID = 4982264981922014374L;  
  3.    
  4.     Sync(int count) {  
  5.         setState(count);//设置AQS的队列状态state字段  
  6.     }  
  7.    
  8.     int getCount() {  
  9.         return getState();  
  10.     }  
  11.    
  12.     protected int tryAcquireShared(int acquires) {  
  13.               //判断AQS的队列状态state值是否变为0,如果不为0,阻塞该线程  
  14.         return (getState() == 0) ? 1 : -1;  
  15.     }  
  16.    
  17.     protected boolean tryReleaseShared(int releases) {  
  18.         // Decrement count; signal when transition to zero  
  19.         for (;;) {  
  20.             int c = getState();  
  21.             if (c == 0)  
  22.                 return false;  
  23.                       //尝试对state字段减1  
  24.             int nextc = c-1;  
  25.             if (compareAndSetState(c, nextc))//CAS原子修改state值  
  26.                 return nextc == 0;  
  27.         }  
  28.     }  
  29. }  

主要的方法就是构造方法Sync(int count)、获取资源方法tryAcquireShared(int acquires)、释放资源方法tryReleaseShared(int releases)。这三个方法分别会被CountDownLatch调用:

 

CountDownLatch构造方法

CountDownLatch的构造方法会调用Sync的构造方法Sync(int count),为AQS的state赋值;

Java代码   收藏代码
  1. public CountDownLatch(int count) {  
  2.         if (count < 0throw new IllegalArgumentException("count < 0");  
  3.         this.sync = new Sync(count);// 调用Sync的构造方法Sync(int count)  
  4.     }  
  5.    

 

CountDownLatch的await方法

CountDownLatch的await方法会调用Sync的tryAcquireShared方法,尝试获取锁,如果获取不到就阻塞;

Java代码   收藏代码
  1. public void await() throws InterruptedException {  
  2.         //AQS的acquireSharedInterruptibly方法内部会调用tryAcquireShared方法  
  3.         sync.acquireSharedInterruptibly(1);  
  4.     }  
  5.    

 

CountDownLatch的countDown方法

CountDownLatch的countDown方法会调用Sync的tryReleaseShared方法释放资源,当state值变为0时,唤醒所有被阻塞的线程。

Java代码   收藏代码
  1. public void countDown() {  
  2.         sync.releaseShared(1);//没调用一次,就会对AQS的state字段减1  
  3. }  
  4.    

 

另外CountDownLatch的await方法还有延迟版本:

Java代码   收藏代码
  1. public boolean await(long timeout, TimeUnit unit)  
  2.         throws InterruptedException {  
  3.         return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));  
  4. }  

 

对应CountDownLatch的两个await方法,也许你已经注意到了 它们都会抛出InterruptedException异常,说明CountDownLatch实现的闭锁是可中断锁,调用线程的interrupt方法,可以中断阻塞的线程。

 

总结

 

 

简单的总结CountDownLatch闭锁就是:它实现了一个“共享锁”,可以阻塞一个或多个线程,以等待另一组事件的发生后,继续执行被阻塞的一个或多个线程。其核心实现就是基于AQS,另外CountDownLatch闭锁是“可中断锁”。

http://moon-walker.iteye.com/blog/2406502

猜你喜欢

转载自aoyouzi.iteye.com/blog/2406632