JUC源码解析CountDownLatch
Posted 林城画序
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC源码解析CountDownLatch相关的知识,希望对你有一定的参考价值。
简介
CountDownLatch,是一个同步器,允许一个或多个线程等待,直到一组操作在其他线程中完成。
概述
初始CountDownLatch时,会给定count,await方法会阻塞,直到count减小到0,countDown方法会是count减1,count不能被重置。
应用
例一
描述
有1个老板,雇了10工人,工人就位后,并不是立即工作,而是等到老板发出指令,才会开始工作,每个工人完成工作后,也会发出一个指令反馈完成此工作,而老板会等待所有的工人都完成工作,然后做下一步打算。
代码
1 public class Driver { 2 private static final int N = 10; 3 4 public static void main(String[] args) throws InterruptedException { 5 CountDownLatch startSignal = new CountDownLatch(1); 6 CountDownLatch doneSignal = new CountDownLatch(N); 7 8 for (int i = 0; i < N; ++i) 9 new Thread(new Worker("[工人" + i + "]", startSignal, doneSignal)).start(); // 启动工作线程 10 11 System.out.println("[老板]发出开始信号"); 12 startSignal.countDown(); // 发出开始信号 13 doneSignal.await(); // 等待工人们完成 14 System.out.println("[老板]收到所有工人完成的信号"); 15 } 16 } 17 18 class Worker implements Runnable { 19 private final String name; 20 private final CountDownLatch startSignal; 21 private final CountDownLatch doneSignal; 22 23 Worker(String name, CountDownLatch startSignal, CountDownLatch doneSignal) { 24 this.name = name; 25 this.startSignal = startSignal; 26 this.doneSignal = doneSignal; 27 } 28 29 public void run() { 30 try { 31 startSignal.await(); // 工人们在此等待老板的开工信号 32 System.out.println(this.name + " >> 开始工作"); 33 doWork(); // 开始做工作 34 System.out.println(this.name + " << 完成工作"); 35 doneSignal.countDown(); // 发出完成信号 36 } catch (InterruptedException ex) { 37 } 38 } 39 40 void doWork() { 41 System.out.println(this.name + " == 正在工作"); 42 } 43 }
输出
[老板]发出开始信号 [工人0] >> 开始工作 [工人0] == 正在工作 [工人0] << 完成工作 [工人2] >> 开始工作 [工人2] == 正在工作 [工人2] << 完成工作 [工人1] >> 开始工作 [工人1] == 正在工作 [工人1] << 完成工作 [工人4] >> 开始工作 [工人3] >> 开始工作 [工人3] == 正在工作 [工人3] << 完成工作 [工人4] == 正在工作 [工人4] << 完成工作 [工人7] >> 开始工作 [工人6] >> 开始工作 [工人5] >> 开始工作 [工人5] == 正在工作 [工人5] << 完成工作 [工人6] == 正在工作 [工人7] == 正在工作 [工人6] << 完成工作 [工人7] << 完成工作 [工人8] >> 开始工作 [工人8] == 正在工作 [工人8] << 完成工作 [工人9] >> 开始工作 [工人9] == 正在工作 [工人9] << 完成工作 [老板]收到所有工人完成的信号
例二
描述
有1个很大的任务,可以分成10个子任务,交给10个线程去工作,并在最后汇总结果。
代码
1 public class Driver2 { 2 private static final int N = 10; 3 4 public static void main(String[] args) throws InterruptedException { 5 CountDownLatch startSignal = new CountDownLatch(1); 6 CountDownLatch doneSignal = new CountDownLatch(N); 7 ExecutorService e = Executors.newFixedThreadPool(N); 8 9 for (int i = 0; i < N; ++i) 10 e.execute(new WorkerRunnable(startSignal, doneSignal, i)); 11 12 e.shutdown(); 13 System.out.println("[总任务]分成" + N + "子任务并开始执行"); 14 startSignal.countDown(); // 发出开始信号 15 doneSignal.await(); 16 System.out.println("[总任务]已经完成"); 17 } 18 } 19 20 class WorkerRunnable implements Runnable { 21 private final CountDownLatch startSignal; 22 private final CountDownLatch doneSignal; 23 private final int i; 24 25 WorkerRunnable(CountDownLatch startSignal, CountDownLatch doneSignal, int i) { 26 this.startSignal = startSignal; 27 this.doneSignal = doneSignal; 28 this.i = i; 29 } 30 31 public void run() { 32 try { 33 startSignal.await(); 34 } catch (InterruptedException e) { 35 } 36 System.out.println("[子任务" + i + "]>>开始执行"); 37 doWork(i); 38 doneSignal.countDown(); 39 System.out.println("[子任务" + i + "]==已经完成"); 40 } 41 42 void doWork(int i) { 43 System.out.println("[子任务" + i + "]==正在执行"); 44 } 45 }
输出
[总任务]分成10子任务并开始执行 [子任务1]>>开始执行 [子任务1]==正在执行 [子任务1]==已经完成 [子任务4]>>开始执行 [子任务4]==正在执行 [子任务4]==已经完成 [子任务3]>>开始执行 [子任务3]==正在执行 [子任务3]==已经完成 [子任务0]>>开始执行 [子任务0]==正在执行 [子任务0]==已经完成 [子任务6]>>开始执行 [子任务6]==正在执行 [子任务6]==已经完成 [子任务2]>>开始执行 [子任务2]==正在执行 [子任务2]==已经完成 [子任务5]>>开始执行 [子任务5]==正在执行 [子任务5]==已经完成 [子任务7]>>开始执行 [子任务8]>>开始执行 [子任务9]>>开始执行 [子任务7]==正在执行 [子任务9]==正在执行 [子任务8]==正在执行 [子任务9]==已经完成 [子任务7]==已经完成 [子任务8]==已经完成 [总任务]已经完成
源码分析
1 public class CountDownLatch { 2 private static final class Sync extends AbstractQueuedSynchronizer { // 内部类,继承自AQS 3 private static final long serialVersionUID = 4982264981922014374L; 4 5 Sync(int count) { // 构造方法 6 setState(count); // 设置state 7 } 8 9 int getCount() { 10 return getState(); // 获取state 11 } 12 13 protected int tryAcquireShared(int acquires) { // 获取共享锁 14 return (getState() == 0) ? 1 : -1; // state为0,成功获得锁,否则失败,去等待 15 } 16 17 protected boolean tryReleaseShared(int releases) { // 释放共享锁 18 for (;;) { 19 int c = getState(); // 获得state 20 if (c == 0) // state为0,已经没有线程在等待,不用唤醒 21 return false; 22 int nextc = c - 1; // state减1 23 if (compareAndSetState(c, nextc)) // 设置state 24 return nextc == 0; // 减到0时,唤醒后面的线程 25 } 26 } 27 } 28 29 private final Sync sync; 30 31 public CountDownLatch(int count) { // 构造方法,给定count 32 if (count < 0) 33 throw new IllegalArgumentException("count < 0"); 34 this.sync = new Sync(count); // count赋值给state,指定需要释放count次锁,才会唤醒所有阻塞在该锁上的线程 35 } 36 37 public void await() throws InterruptedException { 38 sync.acquireSharedInterruptibly(1); // 调用同步器的响应中断的共享锁 39 } 40 41 public boolean await(long timeout, TimeUnit unit) throws InterruptedException { 42 return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); // 调用同步器的支持超时的共享锁 43 } 44 45 public void countDown() { 46 sync.releaseShared(1); // 每调用一次countDown方法,就释放一次锁 47 } 48 49 public long getCount() { 50 return sync.getCount(); // 获取当前state 51 } 52 53 public String toString() { 54 return super.toString() + "[Count = " + sync.getCount() + "]"; 55 } 56 }
CountDownLatch,内部维护一个Sync类,该类继承自AbstractQueuedSynchronizer,所有的逻辑都在Sync类中完成。
当一组线程调用await方法时,其实调用的是Sync的acquireSharedXXX方法,该方法首先判断tryAcquireShared方法的返回值是否大于0(state为0时返回1,否则,返回-1),也就是说,该共享锁是否还有空位,state初始时便会有count设置为大于0的值,所以,一有线程调用await方法(await -> acquireSharedXXX -> tryAcquireShared),其实,进入的是Sync的acquireSharedXXX方法调用的doAcquireSharedXXX方法(该方法在AQS同步器里),进而入队了(寻找安全停靠点停下,或继续抢占共享锁),也就是说,等待了。
再看countDown方法,该方法调用的是Sync的releaseShared方法,该方法会调用tryReleaseShared方法,根据此方法的返回结果,决定是否唤醒阻塞在该共享锁上的线程。查看tryReleaseShared方法的逻辑可知,该方法只在state减到0时,才返回true。当然,如果state已经为0,有线程再调用此方法时,依然返回false,因为,此刻,阻塞在该共享锁上的线程已经被释放过了。
行文至此结束。
尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_cdl.html
以上是关于JUC源码解析CountDownLatch的主要内容,如果未能解决你的问题,请参考以下文章