CountDownLatch(倒计时器)、CyclicBarrier(循环栅栏)
CountDownLatch():
CountDownLatch是一个非常实用的多线程控制工具类,这个工具通常用来控制线程等待,它可以让某一个线程等到倒计时结束,再开始执行。
CountDownLatch的构造函数接收一个整数为参数,即当前这个计数器的计数个数。
public CountDownLatch(int count)
下面演示下CountDownLatch的使用:
1 public class CountDownLatchDemo implements Runnable { 2 3 static CountDownLatch count = new CountDownLatch(10); 4 static CountDownLatchDemo demo = new CountDownLatchDemo(); 5 6 @Override 7 public void run() { 8 try { 9 //模拟检查任务 10 Thread.sleep(new Random().nextInt(10) * 1000); 11 System.out.println("check complete!"); 12 count.countDown(); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 } 17 //测试 18 public static void main(String[] args) throws InterruptedException { 19 ExecutorService exec = Executors.newFixedThreadPool(10); 20 for (int i = 0;i < 10;i++){ 21 exec.submit(demo); 22 } 23 //等待完成所有线程检查 24 System.out.println("开始等待:" + Thread.currentThread().getName()); 25 count.await(); 26 //检查完成 27 System.out.println("检查完成: " + Thread.currentThread().getName()); 28 exec.shutdown(); 29 } 30 }
输出结果:
开始等待:main check complete! check complete! check complete! check complete! check complete! check complete! check complete! check complete! check complete! check complete! 检查完成: main
上述代码的第3行,生成一个CountDownLatch实例,计数数量为10,表示需要有10个线程完成任务,等待在CountDownLatch上的线程才能继续执行。代码第12行,使用了CountDownLatch.countDown()方法,也就是通知CountDownLatch,一个线程已经完成任务,倒计时可以减一了。在第25行,使用了CountDownLatch.await()方法,要求主线程等待所有10个任务完成后,主线程才能继续执行。这点从输出结果也可以看出,主线程一直等到10个线程完成后,才继续执行。
CyclicBarrier():
CyclicBarrier是另外一种多线程并发控制实用工具。和CountDownLatch非常类似,它也可以实现多线程间的计数等待,但它的功能比CountDownLatch更加复杂和强大。比如:我们把计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏的含义。
CyclicBarrier可以接收一个参数做为barrierAction。这个barrierAction就是当计数器完成一次计数后,系统会执行的动作。构造函数如下,其中parties表示计数总数,也就是线程的参与总数。
public CyclicBarrier(int parties,Runnable barrierAction)
下面实例演示:
1 public class CyclicBarrierDemo { 2 3 public static class Soldier implements Runnable{ 4 5 private String soldier; 6 private final CyclicBarrier cyclicBarrier; 7 8 public Soldier(String soldier,CyclicBarrier cyclicBarrier){ 9 this.soldier = soldier; 10 this.cyclicBarrier = cyclicBarrier; 11 } 12 13 @Override 14 public void run() { 15 try{ 16 //等待所有士兵到齐 17 cyclicBarrier.await(); 18 doWork(); 19 //等待所有士兵完成任务 20 cyclicBarrier.await(); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } catch (BrokenBarrierException e) { 24 e.printStackTrace(); 25 } 26 } 27 //任务 28 void doWork(){ 29 try { 30 Thread.sleep(Math.abs(new Random().nextInt() % 10000)); 31 } catch (InterruptedException e) { 32 e.printStackTrace(); 33 }finally { 34 System.out.println(soldier + "完成任务!"); 35 } 36 } 37 //等待完成后,做的事 38 public static class BarrierRun implements Runnable{ 39 40 boolean flag; 41 int N; 42 43 public BarrierRun(boolean flag,int N){ 44 this.flag = flag; 45 this.N = N; 46 } 47 48 @Override 49 public void run() { 50 if (flag){ 51 System.out.println("司令:士兵 " + N + "个,任务完成!"); 52 }else { 53 System.out.println("司令:士兵 " + N + "个,集合完毕!"); 54 flag = true; 55 } 56 } 57 } 58 //测试 59 public static void main(String[] args){ 60 final int N = 10; 61 Thread[] allSoldier = new Thread[N]; 62 boolean flag = false; 63 CyclicBarrier cyclicBarrier = new CyclicBarrier(N,new BarrierRun(flag,N)); 64 //设置屏障点, 主要是为了执行这个方法 65 System.out.println("集合队伍!"); 66 for (int i = 0;i < N;i++){ 67 System.out.println("士兵"+ i + "报道!"); 68 allSoldier[i] = new Thread(new Soldier("士兵"+ i,cyclicBarrier)); 69 allSoldier[i].start(); 70 } 71 } 72 } 73 }
上述代码的第63行,创建了CyclicBarrier实例,并将计数器设置为10,并要求在计数器达到指标后,执行49行run()方法。在第17行,每一个士兵线程都会等待,直到所有的士兵都集合完毕。集合完毕后,意味着CyclicBarrier的一次计数完成,当再一次调用CyclicBarrier.await()时,会进行下一次的计数。第18行,模拟了士兵的任务,当一个士兵完成任务后,代码的第19行就会让CyclicBarrier开始下一次的计数,这次计数的目的就是监控所有士兵是否全部完成了任务。一旦完成,49行的run()方法又会被调用,打印相应的信息。上述代码执行后,输出结果如下:
集合队伍!
士兵0报道!
士兵1报道!
士兵2报道!
士兵3报道!
士兵4报道!
士兵5报道!
士兵6报道!
士兵7报道!
士兵8报道!
士兵9报道!
司令:士兵 10个,集合完毕!
士兵1完成任务!
士兵7完成任务!
士兵9完成任务!
士兵0完成任务!
士兵3完成任务!
士兵4完成任务!
士兵6完成任务!
士兵2完成任务!
士兵5完成任务!
士兵8完成任务!
司令:士兵 10个,任务完成!
可以看出:整个的工作过程是这样的,先开始士兵集合,集合完毕后,打印集合完毕通知;开始任务,进行下一次的计数,所有任务完成后,打印任务完成通知。
注意:
CyclicBarrier.await()方法可能会抛出两个异常。一个是 InterruptedException和 BrokenBarrierException ,InterruptedException表示线程在等待过程中,线程被中断,大部分线程等待的方法,都可能会抛出这个异常;BrokenBarrierException则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有的线程到齐了,如果继续等待,可能就是徒劳无功的,因此,在这个CyclicBarrier上等待的其他线程,都会不再等待,以反常的方式离开。
CountDownLatch和CyclicBarrier的比较
❤ CountDownLatch:是一个(或者多个)线程,等待其他N个线程完成某件事情后才能执行;CyclicBarrier:N个线程互相等待,任何一个线程完成前,所有线程都必须等待。
❤ CountDownLatch:一次性的;CyclicBarrier:循环使用的,可以重复的;
❤ CountDownLatch基于AQS;CyclicBarrier基于锁和Condition;本质上都是依赖于volatile和CAS实现的。
参考:《Java高并发程序设计》 葛一鸣 郭超 编著: