CountDownLatch
简介
CountDownLatch,意思是倒数门闩。它的作用是多个线程做汇聚。主线程开启了 A、B、C 三个线程做不同的事情,但是主线程需要等待 A、B、C 三个线程全部完成后才能继续后面的步骤。此时就需要 CountDownLatch 出马了。CountDownLatch 会阻塞主线程,直到计数走到 0,门闩才会打开,主线程继续执行。而计数递减是每个线程自己操作 CountDownLatch 对象实现的。
这种场景很常见: 篮球比赛中,作为控球后卫,如果没有快攻机会,那就需要等到中锋、大前锋、小前锋、得分后卫都跑到位了,我才能决定怎么组织进攻。又比如我们报团去旅游,必须所有人都到机场了,才能一起出发
对于我们的程序来说这种场景也挺多的,比如你的订单信息可能需要从多个微服务取得数据,汇总后加工才返回给前台。此时从多个微服务取得数据可以是多个子线程来完成。
使用
public static void main(String[] args) throws InterruptedException {
System.out.println("控球后卫到位!等待所有位置球员到位!");
new Thread(()->{
System.out.println("得分后卫到位!");
}).start();
new Thread(()->{
System.out.println("中锋到位!");
}).start();
new Thread(()->{
System.out.println("大前锋到位!");
}).start();
new Thread(()->{
System.out.println("小前锋到位!");
}).start();
System.out.println("全部到位,开始进攻!");
}
这样的运行结果是
没有使用门栓,就无法控制所有线程的阻塞。
接下来看一下有门栓的代码
public class Client {
private static final CountDownLatch countDownLatch = new CountDownLatch(5);
public static void main(String[] args) throws InterruptedException {
System.out.println("控球后卫到位!等待所有位置球员到位!");
countDownLatch.countDown();
new Thread(()->{
System.out.println("得分后卫到位!");
countDownLatch.countDown();
}).start();
new Thread(()->{
System.out.println("中锋到位!");
countDownLatch.countDown();
}).start();
new Thread(()->{
System.out.println("大前锋到位!");
countDownLatch.countDown();
}).start();
new Thread(()->{
System.out.println("小前锋到位!");
countDownLatch.countDown();
}).start();
countDownLatch.await();
System.out.print("全部到位,开始进攻!");
}
}
运行结果如下:
然后我们可以看到,初始的子线程为5,计算器初始也是5,在进攻前设置门栓,也就是countDownLatch.await()。当一个子线程完成是执行countDownLatch.countDown(),计算器减一。必须所有的子线程有执行完毕,计算器为0以后,我们就打开门栓,执行门栓后边的代码。
CyclicBarrier
简介
CountDownLatch让多个线程完成后,再促使主线程继续向下执行。不过它有一定的局限性,无法被重复使用。CyclicBarrier 不会有这个问题。
CyclicBarrier 从字面上理解为循环栅栏。栅栏自然起到的就是屏障的作用,阻止线程通过,而循环则是指其可以反复使用。
使用
出租车司机招揽坐车客人,满四人即可发车,司机每天都可以做很多这样的生意。我们来模拟一下场景
public class Client {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () ->
System.out.println("人满了发车")
);
//模拟十个乘客
IntStream.range(1, 11).forEach(number -> {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
try {
System.out.println("第 " + number + " 乘客上车了!");
cyclicBarrier.await();
System.out.println("第 " + number + " 乘客出发了!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
});
}
首先初始化是计数器为4,并且计数器为零后,打开栅栏,会打印人满了发车。然后是每一个乘客上车都是一个线程,上车后调用 cyclicBarrier.await() 。这里就是屏障点,此时当前线程会阻塞在此处,并且计数器被减 1,每当四个乘客完成上车操作,cyclicBarrier 就会触发 “人满了发车” 的操作。而最后两位乘客上车后,由于没有新的乘客上车,计数器不会被减到 0,导致无法越过屏障,所以永远不会发车。