“J.U.C”:CountDownlatch (r)

来源:互联网 发布:淘宝买iphone8靠谱吗 编辑:程序博客网 时间:2024/05/21 15:50

上篇博文(【Java并发编程实战】—–“J.U.C”:CyclicBarrier)LZ介绍了CyclicBarrier。CyclicBarrier所描述的是“允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务”。而CountDownlatch和它也有一点点相似之处:CountDownlatch所描述的是“在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待”。在JDK API中是这样阐述的:

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。(await()方法是相当于获取锁,条件不满足阻塞,等待释放锁后进行通知,countDown()方法相当于释放锁,然后进行通知等待线程的操作)

CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。

虽然,CountDownlatch与CyclicBarrier有那么点相似,但是他们还是存在一些区别的:

1、CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。

2、 CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

CountDownLatch分析

CountDownLatch结构如下:


从上图中可以看出CountDownLatch依赖Sync,其实CountDownLatch内部采用的是共享锁来实现的(内部Sync的实现可以看出)。它的构造函数如下:

CountDownLatch(int count):构造一个用给定计数初始化的 CountDownLatch。

[java] view plain copy
 print?
  1. public CountDownLatch(int count) {  
  2.         if (count < 0throw new IllegalArgumentException("count < 0");  
  3.         this.sync = new Sync(count);  
  4.     }  

以下源代码可以证明,CountDownLatch内部是采用共享锁来实现的:

[java] view plain copy
 print?
  1. private static final class Sync extends AbstractQueuedSynchronizer {  
  2.         private static final long serialVersionUID = 4982264981922014374L;  
  3.   
  4.         protected int tryAcquireShared(int acquires) {  //具体的获取锁的逻辑就是重写这个方法
  5.             /** 省略源代码 **/  
  6.         }  
  7.   
  8.         protected boolean tryReleaseShared(int releases) {   //具体的释放锁的逻辑就是重写这个方法
  9.            /** 省略源代码 **/  
  10.         }  
  11.     }  

CountDownLatch提供了await方法来实现:

await():使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断

await(long timeout, TimeUnit unit): 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间

[java] view plain copy
 print?
  1. public void await() throws InterruptedException {  
  2.         sync.acquireSharedInterruptibly(1);  
  3.     }  

await内部调用sync的acquireSharedInterruptibly方法:

[java] view plain copy
 print?
  1. public final void acquireSharedInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.         //线程中断,抛出InterruptedException异常  
  4.         if (Thread.interrupted())  
  5.             throw new InterruptedException();  
  6.         if (tryAcquireShared(arg) < 0)  
  7.             doAcquireSharedInterruptibly(arg);  
  8.     }  

acquireSharedInterruptibly()的作用是获取共享锁。如果在获取共享锁过程中线程中断则抛出InterruptedException异常。否则通过tryAcquireShared方法来尝试获取共享锁。如果成功直接返回,否则调用doAcquireSharedInterruptibly方法。

tryAcquireShared源码:

[java] view plain copy
 print?
  1. protected int tryAcquireShared(int acquires) {  
  2.             return (getState() == 0) ? 1 : -1;  
  3.         }  

tryAcquireShared方法被CountDownLatch重写,他的主要作用是尝试着获取锁。getState == 0 表示锁处于可获取状态返回1否则返回-1;当tryAcquireShared返回-1获取锁失败,调用doAcquireSharedInterruptibly获取锁:

[java] view plain copy
 print?
  1. private void doAcquireSharedInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.             //创建当前线程(共享锁)Node节点  
  4.             final Node node = addWaiter(Node.SHARED);  
  5.             boolean failed = true;  
  6.             try {  
  7.                 for (;;) {  
  8.                     //获取当前节点的前继节点  
  9.                     final Node p = node.predecessor();  
  10.                     //如果当前节点为CLH列头,则尝试获取锁  
  11.                     if (p == head) {  
  12.                         //获取锁  
  13.                         int r = tryAcquireShared(arg);  
  14.                         if (r >= 0) {  
  15.                             setHeadAndPropagate(node, r);  
  16.                             p.next = null// help GC  
  17.                             failed = false;  
  18.                             return;  
  19.                         }  
  20.                     }  
  21.                     //如果当前节点不是CLH列头,当前线程一直等待,直到获取锁为止  
  22.                     if (shouldParkAfterFailedAcquire(p, node) &&  
  23.                         parkAndCheckInterrupt())  
  24.                         throw new InterruptedException();  
  25.                 }  
  26.             } finally {  
  27.                 if (failed)  
  28.                     cancelAcquire(node);  
  29.             }  
  30.         }  

该方法当中的方法,前面博客都讲述过,请参考:【Java并发编程实战】—–“J.U.C”:ReentrantLock之二lock方法分析、【Java并发编程实战】—–“J.U.C”:Semaphore。

CountDownLatch,除了提供await方法外,还提供了countDown(),countDown所描述的是“递减锁存器的计数,如果计数到达零,则释放所有等待的线程。”,源码如下:

[java] view plain copy
 print?
  1. public void countDown() {  
  2.         sync.releaseShared(1);  
  3.     }  

countDown内部调用releaseShared方法来释放线程:

[java] view plain copy
 print?
  1. public final boolean releaseShared(int arg) {  
  2.         //尝试释放线程,如果释放释放则调用doReleaseShared()  
  3.         if (tryReleaseShared(arg)) {  
  4.             doReleaseShared();  
  5.             return true;  
  6.         }  
  7.         return false;  
  8.     }  

tryReleaseShared,同时被CountDownLatch重写了:

[java] view plain copy
 print?
  1. protected boolean tryReleaseShared(int releases) {  
  2.         for (;;) {  
  3.             //获取锁状态  
  4.             int c = getState();  
  5.             //c == 0 直接返回,释放锁成功  
  6.             if (c == 0)  
  7.                 return false;  
  8.             //计算新“锁计数器”  
  9.             int nextc = c-1;  
  10.             //更新锁状态(计数器)  
  11.             if (compareAndSetState(c, nextc))  
  12.                 return nextc == 0;  
  13.         }  
  14.     }  

总结:

CountDownLatch内部通过“共享锁”实现。在创建CountDownLatch时,需要传递一个int类型的count参数,该count参数为“锁状态”的初始值,该值表示着该“共享锁”可以同时被多少线程获取。当某个线程调用await方法时,首先判断锁的状态是否处于可获取状态(其条件就是count==0?),如果共享锁可获取则获取共享锁,否则一直处于等待直到获取为止。当线程调用countDown方法时,计数器count – 1。当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。

实例

员工开会只有当所有人到期之后才会开户。我们初始化与会人员为3个,那么CountDownLatch的count应为3:

[java] view plain copy
 print?
  1. public class Conference implements Runnable{  
  2.     private final CountDownLatch countDown;  
  3.       
  4.     public Conference(int count){  
  5.         countDown = new CountDownLatch(count);  
  6.     }  
  7.       
  8.     /** 
  9.      * 与会人员到达,调用arrive方法,到达一个CountDownLatch调用countDown方法,锁计数器-1 
  10.      * @author:chenssy 
  11.      * @data:2015年9月6日 
  12.      * 
  13.      * @param name 
  14.      */  
  15.     public void arrive(String name){  
  16.         System.out.println(name + "到达.....");  
  17.         //调用countDown()锁计数器 - 1  
  18.         countDown.countDown();  
  19.         System.out.println("还有 " + countDown.getCount() + "没有到达...");  
  20.     }  
  21.       
  22.     @Override  
  23.     public void run() {  
  24.         System.out.println("准备开会,参加会议人员总数为:" + countDown.getCount());  
  25.         //调用await()等待所有的与会人员到达  
  26.         try {  
  27.             countDown.await();  
  28.         } catch (InterruptedException e) {  
  29.         }  
  30.         System.out.println("所有人员已经到达,会议开始.....");  
  31.     }  
  32. }  

参加与会人员Participater:

[java] view plain copy
 print?
  1. public class Participater implements Runnable{  
  2.     private String name;  
  3.     private Conference conference;  
  4.       
  5.     public Participater(String name,Conference conference){  
  6.         this.name = name;  
  7.         this.conference = conference;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void run() {  
  12.         conference.arrive(name);  
  13.     }  
  14. }  

Test:

[java] view plain copy
 print?
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         //启动会议室线程,等待与会人员参加会议  
  4.         Conference conference = new Conference(3);  
  5.         new Thread(conference).start();  
  6.           
  7.         for(int i = 0 ; i < 3 ; i++){  
  8.             Participater participater = new Participater("chenssy-0" + i , conference);  
  9.             Thread thread = new Thread(participater);  
  10.             thread.start();  
  11.         }  
  12.     }  
  13. }  

运行结果:

[java] view plain copy
 print?
  1. 准备开会,参加会议人员总数为:3  
  2. chenssy-01到达.....  
  3. 还有 2没有到达...  
  4. chenssy-00到达.....  
  5. 还有 1没有到达...  
  6. chenssy-02到达.....  
  7. 还有 0没有到达...  
  8. 所有人员已经到达,会议开始.....  

0 0