CyclicBarrier vs CountDownLatch
文章目录
1.概述
将比较 CyclicBarrier 和 CountDownLatch,并尝试了解两者之间的异同。
2.这些是什么?
当谈到并发时,概念化每个人打算完成的事情可能具有挑战性。首先,CountDownLatch 和 CyclicBarrier 都用于管理多线程应用程序。而且,它们都旨在表达给定线程或线程组应该如何等待。
2.1. CountDownLatch
CountDownLatch 是一个线程等待而其他线程在闩锁上倒计时直到它达到零的构造。
我们可以把这想象成餐厅正在准备的一道菜。 不管哪位厨师准备了多少n种食物,服务员都必须等到所有的食物都放在盘子上。 如果一个盘子里有 n 件物品,任何厨师都会为她放在盘子上的每件物品倒计时。
2.2. CyclicBarrier
CyclicBarrier 是一种可重用的构造,其中一组线程一起等待,直到所有线程都到达。 此时,屏障被打破,可以选择采取行动。
我们可以把这想象成一群朋友。 每次他们计划在一家餐馆吃饭时,他们都会决定一个共同的见面地点。 他们在那里互相等待,等大家都到齐了,才能一起去餐厅吃饭。
3.Tasks vs Threads
接下来深入地研究这两个类之间的一些语义差异。
如定义中所述,CyclicBarrier 允许多个线程相互等待,而 CountDownLatch 允许一个或多个线程等待多个任务完成。
简而言之,CyclicBarrier 维护线程计数,而 CountDownLatch 维护任务计数。
在下面的代码中,定义了一个计数为 2 的 CountDownLatch。 接下来,从单个线程调用 countDown() 两次:
CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t = new Thread(() -> {
countDownLatch.countDown();
countDownLatch.countDown();
});
t.start();
countDownLatch.await();
assertEquals(0, countDownLatch.getCount());
一旦闩锁达到零,对 await 的调用就会返回。
请注意,在这种情况下,可以让同一线程减少两次计数。
然而,CyclicBarrier 在这一点上有所不同
与上面的示例类似,创建了一个 CyclicBarrier,再次计数为 2 并在其上调用 await(),这次是从同一线程:
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread t = new Thread(() -> {
try {
cyclicBarrier.await();
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// error handling
}
});
t.start();
assertEquals(1, cyclicBarrier.getNumberWaiting());
assertFalse(cyclicBarrier.isBroken());
这里的第一个区别是等待的线程本身就是屏障。
其次,更重要的是,第二个 await() 没用。 单个线程不能对屏障进行两次倒计时。
4.可重用性
在给定的代码中,我们定义了一个计数为 7 的 CountDownLatch 并通过 20 个不同的调用对其进行计数:
List<String> outputScraper = Lists.newArrayList();
CountDownLatch countDownLatch = new CountDownLatch(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
es.execute(() -> {
long prevValue = countDownLatch.getCount();
countDownLatch.countDown();
if (countDownLatch.getCount() != prevValue) {
outputScraper.add("Count Updated");
}
});
}
es.shutdown();
System.out.println(outputScraper.size());//7
观察到,即使 20 个不同的线程调用 countDown(),计数一旦达到零也不会重置。
与上面的例子类似,定义了一个计数为 7 的 CyclicBarrier,并从 20 个不同的线程等待它:
List<String> outputScraper = Lists.newArrayList();
CyclicBarrier cyclicBarrier = new CyclicBarrier(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
es.execute(() -> {
try {
//为0是 CyclicBarrier 重置了
if (cyclicBarrier.getNumberWaiting() <= 0) {
outputScraper.add("Count Updated");
}
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// error handling
}
});
}
es.shutdown();
System.out.println(outputScraper.size());//1
在这种情况下,观察到每次新线程运行时该值都会减少,一旦达到零,就会重置为原始值。
5.总结
总而言之,CyclicBarrier 和 CountDownLatch 都是用于多线程之间同步的有用工具。 但是,它们在提供的功能方面有着根本的不同。 在确定哪个适合工作时,仔细考虑每一个。