[译转]Java CyclicBarrier与CountDownLatch
原文:Java CyclicBarrier与CountDownLatch
1.简介
在本教程中,我们将比较 CyclicBarrier 和 CountDownLatch并尝试了解两者之间的异同。
2.这些是什么?
当涉及到并发时,将每个对象要完成的概念概念化可能是一个挑战。
首先,CountDownLatch和CyclicBarrier都用于管理多线程应用程序。
而且,它们都旨在表示给定线程或线程组应如何等待。
2.1。 CountDownLatch(自减门闩)
一个 CountDownLatch 是一个结构,一个线程等待其他线程 倒计时门闩直至为零。
我们可以将其想象为正在准备的餐厅的一道菜。无论由哪个厨师准备 n种 食物,服务员都必须等到所有食物都放在盘子上。如果一个盘子可 容纳n个 物品,那么任何厨师都会 在他放在盘子上的一个物品的同时对倒计时门闩减一操作。
2.2。 CyclicBarrier(循环阻障)
甲 的CyclicBarrier 是可重复使用的构建体,其中一组线程等待在一起,直到所有线程 到达。在这一点上,所述屏障破裂,一个 动作可任选地服用。
我们可以将其视为一群朋友。每当他们计划在餐厅用餐时,他们都会决定可以见面的共同点。他们在那儿等着 ,只有每个人到了,他们才能一起去餐厅吃饭。
2.3。进一步阅读
有关每个单独组件的更多详细信息,请分别参考我们之前关于CountDownLatch 和CyclicBarrier的教程 。
3.任务与线程
让我们更深入地研究这两个类之间的一些语义差异。
如定义中所述,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());
一旦锁存器达到零,等待 调用就会返回。
请注意,在这种情况下,我们能够使同一线程将计数减少两次。
但是,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());
这里的第一个区别是正在等待的线程就是barrier(障碍)本身。
其次,更重要的是,第二个await() 是无用的。单个线程无法对Barrier(障碍)做两次自减。
确实,因为 t必须 等待另一个线程调用 await() 才能使计数增加到2,所以t的第二次对await()的 调用实际上不会被调用,直到屏障已经被打破!
在我们的测试中,没有越过障碍,因为我们只有一个线程在等待,而没有两个线程将使障碍被触发。从CyclicBarrier.isBroken()方法也很明显,该方法返回false。
4.可重用性
这两个类别之间第二个最明显的区别是可重用性。详细地说,当屏障在CyclicBarrier中跳闸时 ,计数将重置为其原始值。 CountDownLatch是不同的,因为计数永远不会重置。
在给定的代码中,我们定义了一个CountCount为7的CountDownLatch,并通过20个不同的调用对其进行计数:
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(); assertTrue(outputScraper.size() <= 7);
我们观察到,即使有20个不同的线程调用 countDown(),计数一旦达到零也不会重置。
与上面的示例类似,我们定义一个Countic 7的CyclicBarrier 并从20个不同的线程中等待它:
CyclicBarrier cyclicBarrier = new CyclicBarrier(7); ExecutorService es = Executors.newFixedThreadPool(20); for (int i = 0; i < 20; i++) { es.execute(() -> { try { if (cyclicBarrier.getNumberWaiting() <= 0) { outputScraper.add("Count Updated"); } cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { // error handling } }); } es.shutdown(); assertTrue(outputScraper.size() > 7);
在这种情况下,我们观察到每次运行新线程时该值都会减小,一旦达到零,就会重置为原始值。
5.结论
总而言之, CyclicBarrier和CountDownLatch 都是在多个线程之间进行同步的有用工具。但是,它们在功能上根本不同。在确定最适合该工作时,请仔细考虑每个因素。
像往常一样,所有讨论的示例都可以在Github上访问。
其他相关讨论:
Java concurrency: Countdown latch vs Cyclic barrier
- 点赞
- 收藏
- 关注作者
评论(0)