CountDownLatch
一、作用
用于主线程等待多个次线程执行完后再执行。比如API接口要求响应时间在200ms以内,但是如果一个接口内部依赖多个三方/外部服务,那串行调用接口的RT必然很久,所以个人用的最多的是接口RT优化场景,内部服务并行调用。
二、常用方法:
CountDownLatch(int count) //实例化一个倒计数器,count指定计数个数 countDown() // 计数减一 await() //等待,当计数减到0时,所有线程并行执行
三、例子
对于倒计数器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检测。只有等到所有的检查完毕后,引擎才能点火。那么在检测环节当然是多个检测项可以同时进行的。代码实现
public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(5); MyThread my = new MyThread(latch); ExecutorService executor = Executors.newFixedThreadPool(5); for(int i = 0; i < 5; i++) { executor.submit(my); } latch.await(); System.out.println("fire..."); executor.shutdown(); }
public class MyThread implements Runnable{ private CountDownLatch countDownLatch; public MyThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { Thread.sleep(5000L); System.out.println("yoxi"); } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown(); } } }
打印:
yoxi
yoxi
yoxi
yoxi
yoxi
fire...
CyclicBarrier
CyclicBarrier是另外一种多线程并发控制实用工具。和CountDownLatch非常类似,它也可以实现线程间的计数等待,但它的功能比CountDownLatch更加复杂且强大。
CyclicBarrier可以理解为循环栅栏。栅栏就是一种障碍物,比如,通常在私人宅邸的周围就可以围上一圈栅栏,阻止闲杂人等入内。这里当然就是用来阻止线程继续执行,要求线程在栅栏处等待。前面Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,然后接着凑齐下一批10个线程,这就是循环栅栏内在的含义。
CyclicBarrier的使用场景也很丰富。比如,司令下达命令,要求10个士兵一起去完成一项任务。这时,就会要求10个士兵先集合报道,接着,一起雄赳赳气昂昂地去执行任务。当10个士兵把自己手头的任务都执行完成了,那么司令才能对外宣布,任务完成!
比CountDownLatch略微强大一些,CyclicBarrier可以接收一个参数作为barrierAction。所谓barrierAction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示计数总数,也就是参与的线程总数。
public CyclicBarrier(int parties, Runnable barrierAction)
下面的示例使用CyclicBarrier演示了上述司令命令士兵完成任务的场景。
public class CyclicBarrierDemo { public static class Soldier implements Runnable { private String soldier; private final CyclicBarrier cyclic; Soldier(CyclicBarrier cyclic, String soldierName) { this.cyclic = cyclic; this.soldier = soldierName; } public void run() { try { // 等待所有士兵到齐 cyclic.await(); doWork(); // 等待所有士兵完成工作 cyclic.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } void doWork() { try { Thread.sleep(Math.abs(new Random().nextInt() % 10000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(soldier + ":任务完成"); } } public static class BarrierRun implements Runnable { boolean flag; int N; public BarrierRun(boolean flag, int N) { this.flag = flag; this.N = N; } public void run() { if (flag) { System.out.println("司令:[士兵" + N + "个,任务完成!]"); } else { System.out.println("司令:[士兵" + N + "个,集合完毕!]"); flag = true; } } } public static void main(String args[]) throws InterruptedException { final int N = 10; Thread[] allSoldier=new Thread[N]; boolean flag = false; CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N)); //设置屏障点,主要是为了执行这个方法 System.out.println("集合队伍!"); for (int i = 0; i < N; ++i) { System.out.println("士兵 "+i+" 报道!"); allSoldier[i]=new Thread(new Soldier(cyclic, "士兵 " + i)); allSoldier[i].start(); } } }
输出如下:
集合队伍! 士兵 0 报道! //篇幅有限,省略其他几个士兵 士兵 9 报道! 司令:[士兵10个,集合完毕!] 士兵 0:任务完成 //篇幅有限,省略其他几个士兵 士兵 4:任务完成 司令:[士兵10个,任务完成!]

CyclicBarrier.await()方法可能会抛出两个异常。一个是InterruptedException,也就是在等待过程中,线程被中断,应该说这是一个非常通用的异常。大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。另外一个异常则是CyclicBarrier特有的BrokenBarrierException。一旦遇到这个异常,则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有线程到齐了。如果继续等待,可能就是徒劳无功的,因此,还是就地散货,打道回府吧!