Java中CountDownLatch类分析

0、CountDownLatch作用

 1) Java api中的解释:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

   2) CountDownLatch可以使Java线程阻塞在某个地方,当达到某个条件时(CountDownLatch的等待计数为0),线程才继续往后执行。

1、实例 (参考http://blog.csdn.net/shihuacai/article/details/8856370)

  1) 需求

  10个运动员进行100米赛跑,要求裁判发出命令时,比赛开始,并打印出10名选手到达终点的顺序。

  2) 代码

  1. 1 import java.util.concurrent.CountDownLatch;
  2. 2 import java.util.concurrent.ExecutorService;
  3. 3 import java.util.concurrent.Executors;
  4. 4
  5. 5
  6. 6 public class CountDownLatchTest {
  7. 7 // 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
  8. 8 public static void main(String[] args) throws InterruptedException {
  9. 9 // 开始的倒数锁
  10. 10 CountDownLatch begin = new CountDownLatch(1);
  11. 11 // 结束的倒数锁
  12. 12 CountDownLatch end = new CountDownLatch(10);
  13. 13 // 十名选手
  14. 14 ExecutorService exec = Executors.newFixedThreadPool(10);
  15. 15 for (int index = 0; index < 10; index++) {
  16. 16 final int NO = index + 1;
  17. 17 Runnable run = new Runnable() {
  18. 18 public void run() {
  19. 19 try {
  20. 20 // 等待
  21. 21 begin.await();
  22. 22 System.out.println("选手" + NO + " 到达终点");
  23. 23 } catch (Exception e) {
  24. 24 } finally {
  25. 25 // 每个选手到达终点时,end就减一
  26. 26 end.countDown();
  27. 27 }
  28. 28 }
  29. 29 };
  30. 30 exec.submit(run);
  31. 31 }
  32. 32 System.out.println("Game Start");
  33. 33 // begin减一,开始游戏
  34. 34 begin.countDown();
  35. 35 // 等待end变为0,即所有选手到达终点
  36. 36 end.await();
  37. 37 System.out.println("Game Over");
  38. 38 exec.shutdown();
  39. 39 }
  40. 40 }

  3) 解释

  • 第10行,12行分别创建2个CountDownLatch,倒数锁分别为1和10,
  • 第15~31行的for循环,定义10是Runnable任务,每个任务代表一名选手到达终点。
  • 第30(exec.submit(run))行是将创建的10个Runnable任务提交至线程池。每次将任务提交线程池(exec.submit(run)),该任务都会阻塞在第21行处(begin.await(),因为此时begin的倒数锁是1,不是0),for循环执行完毕后,线程池exec中装了10个Ruunable任务,每个任务都阻塞在begin.await()处。
  • 第34行(begin.countDown())执行完成后,begin的倒数锁变为0,此时线程池exec中阻塞的10个任务并发执行,而主线程则阻塞在第36行(end.await(),因为end的倒数锁不为0),每执行完一个,end的倒数锁减一,10个任务全部执行完成后,end倒数锁变为0,主线程继续执行,打印Game Over。

  4) 结果

       

  5) 相关分析

  若注释掉第21行(begin.await()),则在for循环中,每创建一个Runnable后,会提交至线程池,该Runnable便会执行,所以预期结果会是顺序打印1至10号选手,但结果如下,并不是顺序打印1至10号选手

  

  可以发现,虽然不是顺序打印1至10号选手,但总体打印顺序基本是从小到大(可执行多次验证),这是因为多线程的结果(10个任务虽然按选手1~10顺序提交,但因为并行执行任务,所以并不会完全顺序打印1至10号选手,但总体还是呈现任务早提交,早执行的现象)。

  为此我们在程序中增加一行,第31行,并注释掉第21行,代码如下

  1. 1 import java.util.concurrent.CountDownLatch;
  2. 2 import java.util.concurrent.ExecutorService;
  3. 3 import java.util.concurrent.Executors;
  4. 4
  5. 5
  6. 6 public class CountDownLatchTest {
  7. 7 // 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
  8. 8 public static void main(String[] args) throws InterruptedException {
  9. 9 // 开始的倒数锁
  10. 10 CountDownLatch begin = new CountDownLatch(1);
  11. 11 // 结束的倒数锁
  12. 12 CountDownLatch end = new CountDownLatch(10);
  13. 13 // 十名选手
  14. 14 ExecutorService exec = Executors.newFixedThreadPool(10);
  15. 15 for (int index = 0; index < 10; index++) {
  16. 16 final int NO = index + 1;
  17. 17 Runnable run = new Runnable() {
  18. 18 public void run() {
  19. 19 try {
  20. 20 // 等待
  21. 21 // begin.await();
  22. 22 System.out.println("选手" + NO + " 到达终点");
  23. 23 } catch (Exception e) {
  24. 24 } finally {
  25. 25 // 每个选手到达终点时,end就减一
  26. 26 end.countDown();
  27. 27 }
  28. 28 }
  29. 29 };
  30. 30 exec.submit(run);
  31. 31 Thread.sleep(1000);
  32. 32 }
  33. 33 System.out.println("Game Start");
  34. 34 // begin减一,开始游戏
  35. 35 begin.countDown();
  36. 36 // 等待end变为0,即所有选手到达终点
  37. 37 end.await();
  38. 38 System.out.println("Game Over");
  39. 39 exec.shutdown();
  40. 40 }
  41. 41 }

  这样,每个Runnable任务提交至线程池后会等待1s,运行结果如下:

      

  该结果10个Runnable任务顺序执行,因为每个任务提交至线程池后等待一秒,在这一秒内,该Runnable任务已经执行完成,所以10个任务会顺序执行。

  当取消注释第21行时,运行结果如下:

  

  此时,10个Runnable任务没有顺序执行,是因为每次讲任务提交至线程池后,虽然等待1秒,但该任务运行至第21行时,会阻塞住,所以for循环执行完后,线程池中会有10个Runnable任务,这10个任务全都阻塞在第21行(begin.await()),当主线程运行完第35行后,begin倒数锁变为0,被阻塞的10个Runnable任务并行执行,所以运行结果是无序的。

 

版权声明:本文为sunseine原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/sunseine/p/5853494.html