CyclicBarrier及CountDownLatch的使用

Posted 前度刘郎

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CyclicBarrier及CountDownLatch的使用相关的知识,希望对你有一定的参考价值。

CountDownLatch位于java.util.concurrent包下,是JDK1.5的并发包下的新特性。

首先根据Oracle的官方文档看看CountDownLatch的定义:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

 

简单来说,CountDownLatch是一个同步的辅助类,允许一个或多个线程一直等待,直到其它线程完成它们的操作。

这里就涉及两个问题:

1.如何让一个或多个线程一直等待;

2.如何让这些线程知道其它线程已经完成它们的操作

 

这两个问题主要是使用一个count的属性解决。使用count初始化CountDownLatch,然后需要等待的线程调用await方法。await方法会一直受阻塞直到count=0。

而其它线程完成自己的操作后,调用countDown()使计数器count减1。当count减到0时,所有在等待的线程均会被释放,并且count无法被重置。如果需要重置,请参考CyclicBarrier 

 

假设有以下的场景,3个学生做试卷考试,老师要等所有学生做完试卷后才收卷。具体代码如下:

先看学生Student.java:

复制代码
 1 import java.util.Random;
 2 import java.util.concurrent.CountDownLatch;
 3 import java.util.concurrent.TimeUnit;
 4 
 5 public class Student implements Runnable {
 6     
 7     private int num;
 8     private CountDownLatch cdlatch;
 9     
10     Student(int num,CountDownLatch latch){
11         this.num = num;
12         this.cdlatch = latch;
13     }
14 
15     @Override
16     public void run() {
17         doExam();
18         try {
19             TimeUnit.SECONDS.sleep(new Random().nextInt(10));
20         } catch (InterruptedException e) {
21             // TODO Auto-generated catch block
22             e.printStackTrace();
23         }
24         System.out.println("Student "+num+" finished!");
25         cdlatch.countDown();
26     }
27     
28     private void doExam(){
29         System.out.println("Student "+num+" is doing the exam!");
30     }
31     
32 }
复制代码

 

再看老师Teacher.java:

复制代码
 1 import java.util.concurrent.CountDownLatch;
 2 
 3 public class Teacher implements Runnable{
 4     
 5     private CountDownLatch cdlatch;
 6     
 7     Teacher(CountDownLatch latch){
 8         this.cdlatch = latch;
 9     }
10     
11     @Override
12     public void run() {
13         // TODO Auto-generated method stub
14         try {
15             System.out.println("teacher is waiting...");
16             cdlatch.await();
17             System.out.println("teacher is collecting......");
18         } catch (InterruptedException e) {
19             // TODO Auto-generated catch block
20             e.printStackTrace();
21         }
22     }
23 
24 }
复制代码
复制代码
 1 import java.util.concurrent.CountDownLatch;
 2 import java.util.concurrent.ExecutorService;
 3 import java.util.concurrent.Executors;
 4 
 5 public class TestCountDownLatch {
 6 
 7     public static void main(String[] args) {
 8 
 9         ExecutorService executor = Executors.newCachedThreadPool();
10         
11         CountDownLatch latch = new CountDownLatch(3);
12         
13         Student s1 = new Student(101, latch);
14         Student s2 = new Student(102, latch);
15         Student s3 = new Student(103, latch);
16         Teacher t = new Teacher(latch);
17         
18         executor.execute(t);
19         executor.execute(s1);
20         executor.execute(s2);
21         executor.execute(s3);
22         
23         executor.shutdown();
24         
25     }
26 
27 }
复制代码

 

我们可以看到运行的结果:

teacher is waiting...
Student 101 is doing the exam!
Student 102 is doing the exam!
Student 103 is doing the exam!
Student 102 finished!
Student 101 finished!
Student 103 finished!
teacher is collecting......

 

再来看一个稍微复杂点的例子,10个选手比赛跑步,在枪响后同时起跑,全部到达终点后比赛结束:

复制代码
 1 import java.util.concurrent.CountDownLatch;
 2 import java.util.concurrent.ExecutorService;
 3 import java.util.concurrent.Executors;
 4 
 5 
 6 public class CountDownLatchDemo {
 7 
 8     private static int PLAYER_NUM = 10;
 9     
10     public static void main(String[] args) {
11         
12         final CountDownLatch beginSignal = new CountDownLatch(1);
13         final CountDownLatch endSignal = new CountDownLatch(PLAYER_NUM);
14         
15         ExecutorService executorService = Executors.newFixedThreadPool(PLAYER_NUM);
16         
17         for(int i=0;i<PLAYER_NUM;i++){
18             final int num = i+1;
19             Runnable runner = new Runnable(){
20 
21                 @Override
22                 public void run() {
23                     // TODO Auto-generated method stub
24                     System.out.println("No. "+num+" is waiting...");
25                     try {
26                         beginSignal.await();
27                         System.out.println("No. "+num+" begin running");
28                         Thread.sleep((long) (Math.random() * 10000));
29                         System.out.println("No." + num + " arrived");
30                     } catch (InterruptedException e) {
31                         // TODO Auto-generated catch block
32                         e.printStackTrace();
33                     }finally{
34                         endSignal.countDown();
35                     }
36                 }
37                 
38             };
39             executorService.execute(runner);
40         }
41         
42         System.out.println("before Game Start");
43         beginSignal.countDown();
44         System.out.println("Game Start");
45         System.out.println("---In the middle of the game---");
46         try {
47             endSignal.await();
48         } catch (InterruptedException e) {
49             // TODO Auto-generated catch block
50             e.printStackTrace();
51         }finally{
52             System.out.println("Game Over!");
53             executorService.shutdown();
54         }
55 
56     }
57 
58 }
复制代码

 

以上逻辑不难理解,beginSignal的count=0时,runner线程开始运行,直到endSignal的count=0时结束。

接下来分析一下运行6次的结果:

 

可以看到,因为有beginSignal,所以可以保证所有runner都waiting以后,才begin running。同理,因为有endSignal,可以保证所有runner arrived后才Game Over!

但是,这里的需要留意主线程的几个输出:

1 System.out.println("before Game Start");
2 beginSignal.countDown();
3 System.out.println("Game Start");
4 System.out.println("---In the middle of the game---");

1.尽管before Game Start在countDown()之前,但不能保证is waiting全部输出完后,才输出before Game Start。

2.“Game Start”和"In the middlel of the game"虽然都在countDown()之后,但在多线程的环境下(主线程也是线程之一),无法预计两个字段输出的位置。从上面的case看,有可能在running的前面,中间和后面,无法预计。这里要十分注意。

3.因为有Thread.sleep,所以arrived都在running之后出现。否则,arrived出现的位置,就不一定都在running之后了。

 

参考:

http://www.cnblogs.com/shiyanch/archive/2011/04/04/2005233.html

http://blackgu.blogbus.com/logs/69596661.html

 

 

 

CyclicBarrier和CountDownLatch一样,都是关于线程的计数器。

用法略有不同,测试代码如下:

复制代码
 1 public class TestCyclicBarrier {
 2 
 3     private static final int THREAD_NUM = 5;
 4     
 5     public static class WorkerThread implements Runnable{
 6 
 7         CyclicBarrier barrier;
 8         
 9         public WorkerThread(CyclicBarrier b){
10             this.barrier = b;
11         }
12         
13         @Override
14         public void run() {
15             // TODO Auto-generated method stub
16             try{
17                 System.out.println("Worker\'s waiting");
18                 //线程在这里等待,直到所有线程都到达barrier。
19                 barrier.await();
20                 System.out.println("ID:"+Thread.currentThread().getId()+" Working");
21             }catch(Exception e){
22                 e.printStackTrace();
23             }
24         }
25         
26     }
27     
28     /**
29      * @param args
30      */
31     public static void main(String[] args) {
32         // TODO Auto-generated method stub
33         CyclicBarrier cb = new CyclicBarrier(THREAD_NUM, new Runnable() {
34             //当所有线程到达barrier时执行
35             @Override
36             public void run() {
37                 // TODO Auto-generated method stub
38                 System.out.println("Inside Barrier");
39                 
40             }
41         });
42         
43         for(int i=0;i<THREAD_NUM;i++){
44             new Thread(new WorkerThread(cb)).start();
45         }
46     }
47 
48 }
49 /*
50 以下是输出:
51 Worker\'s waiting
52 Worker\'s waiting
53 Worker\'s waiting
54 Worker\'s waiting
55 Worker\'s waiting
56 Inside Barrier
57 ID:12 Working
58 ID:8 Working
59 ID:11 Working
60 ID:9 Working
61 ID:10 Working
62 */
复制代码
  1.  CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 
  2.  CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。 
  3.  CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。

以上是关于CyclicBarrier及CountDownLatch的使用的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程实战|CyclicBarrier原理介绍及使用场景

大白话讲解JDK源码系列:线程并发协调神器CountDownLatch和CyclicBarrier

java多线程系列:一 并发工具类的使用_2 ( CountDownLatch CyclicBarrier Semaphore Exchanger )

Java CyclicBarrier

CyclicBarrier如何使用?

CyclicBarrier源码分析