并发编程学习笔记(十九、CountDownLatch源码分析)
目录:
- CountDownLatch是什么
- 为什么要有CountDownLatch
- CountDownLatch源码分析
CountDownLatch是什么
CountDownLatch是一种闭锁,也叫倒数计数器,它可以等待多个线程执行完毕后再执行某一件事情。
比如你app的首页要加载很多个模块,而这些模块又处于不同服务,这时候你就可以开启多个线程去分别调用这些模块;然后你后续的一个操作需要用到刚刚调用的那些服务返回的数据,这时候你就可以用CountDownLatch了。
为什么要有CountDownLatch
针对上面那个问题你可能会说,那直接用Thread.join()不就可以了嘛。当然就简单的场景来说,join()的确就够用了,而CountDownLatch其实也就是对join()的一种扩展,它可以适用于更复杂的场景。
我们通过一段代码来看看为何CountDownLatch的控制粒度更细(为了方便我把两个demo的代码写一块了)。
1 public class ThreadA extends Thread { 2 3 private CountDownLatch countDownLatch; 4 5 public ThreadA() { 6 } 7 8 public ThreadA(CountDownLatch countDownLatch) { 9 this.countDownLatch = countDownLatch; 10 } 11 12 @Override 13 public void run() { 14 System.out.println("threadA start..."); 15 16 try { 17 System.out.println("threadA 开始执行第一个任务..."); 18 TimeUnit.SECONDS.sleep(1); 19 System.out.println("threadA 第一个任务执行完毕..."); 20 } 21 catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 finally { 25 if (countDownLatch != null) { 26 countDownLatch.countDown(); 27 } 28 } 29 30 try { 31 System.out.println("threadA 开始执行第二个任务..."); 32 TimeUnit.SECONDS.sleep(3); 33 System.out.println("threadA 第二个任务执行完毕..."); 34 } 35 catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 finally { 39 // do something... 40 } 41 42 System.out.println("threadA end..."); 43 } 44 45 }
1 public class ThreadB extends Thread { 2 3 private CountDownLatch countDownLatch; 4 5 public ThreadB() { 6 } 7 8 public ThreadB(CountDownLatch countDownLatch) { 9 this.countDownLatch = countDownLatch; 10 } 11 12 @Override 13 public void run() { 14 System.out.println("threadB start..."); 15 16 try { 17 System.out.println("threadB 开始执行第一个任务..."); 18 TimeUnit.SECONDS.sleep(2); 19 System.out.println("threadB 第一个任务执行完毕..."); 20 } 21 catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 finally { 25 if (countDownLatch != null) { 26 countDownLatch.countDown(); 27 } 28 } 29 30 try { 31 System.out.println("threadB 开始执行第二个任务..."); 32 TimeUnit.SECONDS.sleep(5); 33 System.out.println("threadB 第二个任务执行完毕..."); 34 } 35 catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 finally { 39 // do something... 40 } 41 42 System.out.println("threadB end..."); 43 } 44 45 }
1 public class JoinTest { 2 3 public static void main(String[] args) throws InterruptedException { 4 ThreadA threada = new ThreadA(); 5 ThreadB threadb = new ThreadB(); 6 threada.start(); 7 threadb.start(); 8 threada.join(); 9 threadb.join(); 10 System.out.println("joinTest end..."); 11 } 12 13 }
1 public class CountDownLatchTest { 2 3 public static void main(String[] args) throws InterruptedException { 4 CountDownLatch countDownLatch = new CountDownLatch(2); 5 ExecutorService executorService = Executors.newCachedThreadPool(); 6 executorService.submit(new ThreadA(countDownLatch)); 7 executorService.submit(new ThreadB(countDownLatch)); 8 countDownLatch.await(); 9 System.out.println("countDownLatchTest end..."); 10 executorService.shutdown(); 11 } 12 13 }
为了方便我直接把countDownLatch.countDown()写到一个thread里面了,哈哈。
首先我们观察ThreadA与ThreadB,可以发现他们分别有两个任务需要完成;如果你的业务场景都是需要在两个线程执行完后才会处理数据,那么join和CountDownLatch都可以实现。
但如果你的场景和上述一样,只要在ThreadA、ThreadB完成第一个任务就可以处理后续逻辑的话,那么你就需要使用到CountDownLatch了;因为join没法处理那种线程只执行到某个节点就能唤醒的操作,它必须要当线程全部执行完后才能够唤醒,这就是为什么说CountDownLatch的控制粒度要比join细的原因了。
CountDownLatch源码分析
如果你把我前面所讲的AQS弄懂,那这个简直就是轻而易举,我这里就不再赘述了(偷懒了,哈哈)。
查阅源码的时候你只要记住它的实现原理就可以了,CountDownLatch可以等待多个线程执行完毕后再执行某一件事情。