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无法被重置。如果需要重置,请参考
假设有以下的场景,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
以上逻辑不难理解,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之后了。
参考:
CyclicBarrier和一样,都是关于线程的计数器。
用法略有不同,测试代码如下:
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
- CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
- CyclicBarrier就象它名字的意思一样,可看成是个障碍, 所有的线程必须到齐后才能一起通过这个障碍。
- CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。