我们的一位培训师在解释 CountDownLatch 和 CyclicBarrier 之间的区别时给出了一个示例。
CountDownLatch
:假设一块石头可以由 10 个人举起,那么您将等待所有 10 个人来。然后只有你可以举起石头。
CyclicBarrier
: 如果你要去野餐,你需要先在某个共同点见面,从那里开始你的旅程。
如果有人同意这些意见,请给我一些细节。
我已经阅读了这两个类的 sun API。但我需要更多解释。
我们的一位培训师在解释 CountDownLatch 和 CyclicBarrier 之间的区别时给出了一个示例。
CountDownLatch
:假设一块石头可以由 10 个人举起,那么您将等待所有 10 个人来。然后只有你可以举起石头。
CyclicBarrier
: 如果你要去野餐,你需要先在某个共同点见面,从那里开始你的旅程。
如果有人同意这些意见,请给我一些细节。
我已经阅读了这两个类的 sun API。但我需要更多解释。
在假设的剧院中:
在这里,一个人是一个线程,一个游戏是一个资源。
主要区别在于CountDownLatch
将线程分为等待者和到达者,而所有使用 a 的线程CyclicBarrier
都执行这两种角色。
您的闩锁示例意味着所有十个人必须等待一起举起石头。不是这种情况。一个更好的现实世界的例子是一个考试提示者,他耐心地等待每个学生提交他们的考试。学生完成考试后无需等待,可以自由离开。一旦最后一名学生提交考试(或时间限制到期),提示者将停止等待并带着考试离开。
真实世界的例子我可以看到所有的答案实际上都缺少一个真实的例子。就像这些类如何在软件领域中使用一样
CountDownLatch 一个多线程下载管理器。下载管理器会启动多个线程同时下载文件的各个部分。(前提是服务器支持多线程下载)。这里每个线程都会调用一个实例化的锁存器的倒计时方法。在所有线程执行完毕后,与倒计时锁相关的线程会将在不同部分中找到的部分整合到一个文件中
CyclicBarrier 与上述相同的场景..但假设文件是从 P2P 下载的。再次多个线程下载这些片段。但是在这里,假设您希望在特定时间间隔后对下载的片段进行完整性检查。在这里,循环屏障起着重要作用。在每个时间间隔之后,每个线程都会在屏障处等待,以便与 cyclibarrier 关联的线程可以进行完整性检查。由于 CyclicBarrier,这种完整性检查可以进行多次
如果有任何不妥之处,请纠正我。
用例 1 假设您将一个大作业拆分为 10 个小任务,每个小任务一个线程。在考虑完成工作之前,您必须等待该线程的 10 个任务结束。
因此,主作业发起者线程将 CountDownLatch 初始化为使用的线程数,它将任务分配给线程并等待闩锁通过await
方法提高为零。countDown
每个执行器线程将在其任务结束时调用。最后,当所有线程都完成后,主线程将被唤醒,因此它认为整个工作已经完成。此方案使用doneSignal
CountDownLatch javadoc 中描述的锁存器。
用例 2假设您将一个大型作业拆分为一个 * m 个任务,分布在 n 个线程上。m 对应于矩阵行,您需要计算每一行的总数。在这种情况下,必须在每个任务结束后同步线程,以便处理行的总数。在这种情况下,使用CyclicBarrier
线程数 n 初始化的 a 用于等待每一行计算的结束(实际上是 m 次)。
为了比较两者,CountDownLatch
应该只使用一次,并且CyclicBarrier
可以使用任意多次,因为算法需要一组线程的同步点。
ACyclicBarrier
是可重复使用的,因此它更像是一场赛车巡回赛,每个人都在一个航路点相遇,然后继续下一段巡回赛。
CountDownLatch: 如果我们想让我们所有的线程都做
某事+倒计时
为了让其他等待(计数达到零)线程可以继续,我们可以使用倒计时锁存器。在这种情况下,实际上执行倒计时的所有先前线程都可以继续,但不能保证在latch.countdown() 之后处理的行将在等待其他线程到达latch.countdown() 之后 ,但它可以保证其他线程等待线程只会在latch.await() 达到零后进一步启动。
CyclicBarrier: 如果我们希望所有线程
做某事+共同等待+做某事
(每次等待调用都会减少线程继续执行的等待时间)
CyclicBarrier 功能只能由 CountDownLatch 实现一次,方法是调用latch.countdown(),然后由所有线程调用latch.await()。
但同样你不能重置/重用倒计时锁。
我使用 CyclicBarrier 的最佳示例是初始化多个缓存(由多个线程加热),然后开始进一步处理,我想在 Sync 中再次重新初始化其他缓存。
理论差异:
在 CountDownLatch 中,主线程等待其他线程完成它们的执行。在 CyclicBarrier 中,工作线程互相等待完成它们的执行。
一旦计数达到零并且锁存器打开,您就不能重用相同的 CountDownLatch 实例,另一方面,可以通过重置屏障来重用 CyclicBarrier,一旦屏障被破坏。
现实生活中的例子:--
CountDownLatch:考虑一个 IT 世界场景,经理在开发团队(A 和 B)之间划分模块,并且他希望仅在两个团队都完成任务时将其分配给 QA 团队进行测试。
这里经理线程作为主线程工作,开发团队作为工作线程工作。经理线程等待开发团队线程完成他们的任务。
CyclicBarrier:考虑相同的 IT 世界场景,经理在开发团队(A 和 B)之间划分模块。他请假,并要求两个团队在完成各自的任务后互相等待,然后将其分配给 QA 团队进行测试。
这里经理线程作为主线程工作,开发团队作为工作线程工作。开发团队线程在完成任务后等待其他开发团队线程。
CyclicBarrier:在Ludo Game APP中,直到所有玩家都加入游戏才能开始四人游戏。让我们假设所有四个玩家都是一个单独的线程。在这种情况下,玩家(线程)必须等待所有其他线程(玩家)加入游戏,每个线程(玩家)加入游戏后都会调用 await() 方法。只有在定义数量的线程调用 await() 方法后,它们才能继续执行。即所有玩家都加入了游戏。
CountDownLatch:在一家 IT 公司,假设正在进行一项针对四人团队的调查,经理需要将所有员工的反馈提交给更高的管理层。在这种情况下,所有四名员工都是线程,他们将提供反馈并调用 countDown() 方法。直到并且除非所有员工都没有提交他们的反馈或达到最后期限,否则经理线程必须通过调用 await() 方法等待。在得到员工的反馈后,经理可以将反馈提交给更高的管理层。在 countDownLatch 的情况下,线程不需要相互等待,它们可以在调用 countDown() 方法后继续执行。即员工可以继续编写程序,也可以在提交反馈后去吃午饭。
对于 CyclicBarrier,我能想到的一个实时示例;让我们想象一下有一群游客乘坐节奏旅行者。一天有好几个地方要去。节奏驱动器知道有 x 个游客。一旦到达第一个地点,所有的游客在不同的时间点出去和回来;然而,节奏和旅行者必须等到所有游客返回。一旦它们全部返回,驱动程序就会继续到下一个位置,并重复相同的过程。在这里,CyclicBarrier 被初始化为游客数量。每个游客都像一个线程,在返回时,他们调用 CyclicBarrier await() 以便他们等到所有其他游客都回来。现在告诉我你的想法
顾名思义,循环障碍可以在循环中使用。例如:我是一家公司人力资源部,正在从各种工作门户供稿中寻找 N 份简历。我有一个技能组数组,其中包含按优先级排序的技能。对于前 java、c#、python。我想找到 N 个匹配 java 技能组的简历,但如果我没有找到所需的编号。的简历,我再次搜索下一个技能,依此类推。
我创建了一个工作人员,每个工作人员在分配的工作提要中扫描简历。两位员工都将从他们的工作提要中的主要技能集搜索开始。
执行搜索后,工作人员将检查是否找到了 N 份简历。如果找到,worker 将重置屏障并返回。否则它将等待其他工作人员完成。如果仍然没有找到 N 份简历,则将在技能组数组中的下一个技能上重新开始搜索。因此,可以递归/循环调用搜索,而无需创建新的循环屏障。
以下是我的观察: -----> 1. 在哪里使用什么:在循环屏障中,线程必须等待其他线程给出一些输出,然后所有线程都必须恢复处理。所以在完成执行后,每个线程都会调用 await() 方法并等待。当屏障检测到所有线程都到达它时,它会通知所有等待的线程,它们可以继续执行。屏障跟踪计数。
在 CountDownLatch 中,单个主线程等待所有线程完成。每个线程在完成执行后将计数减 1。计数达到 0 后,主线程可以恢复进一步执行。主线程跟踪计数。
Phaser:在这两种方式中,线程数都应该事先知道。无法动态添加/删除线程。如果未达到计数,则跟踪计数的线程将无限等待。使用 Phaser,线程的数量可以是动态的并且随时间变化。它类似于循环障碍。线程注册一个屏障。完成执行后,它有两个选项。它可以发出已经到达障碍点的信号,并且无需等待其他人就可以从 Phaser 注销。第二种选择是它可以等待其他注册的线程到达障碍点。
计数:在创建 CyclicBarrier 时,工作线程的数量包括主线程,如果它也将等待其他线程完成。创建 CountDownLatch 时只需要提及主线程将等待完成的工作线程数。在 CountDownLatch 中有主线程和工作线程的概念,并且在创建锁存器时主等待线程不包括在计数中。Phaser 可以返回当前注册线程的计数。
await() 的意图:在 CyclicBarrier :: await() 中,包括主线程在内的所有线程都是平等的并相互等待。因此 await() 应该在所有线程(工作线程和主线程)中给出。CountDownLatch :: await() 仅在主线程中给出,它使主线程等待直到其他工作线程计数为 0。因此两个 await() 的内部实现是不同的。Phaser :: 到达屏障有两个概念(arrive() 和arriveAndDeregister()) 和waiting(awaitAdvance(phase_number)) 用于其他线程。
各方和等待线程:CountDownLatch 不能给出等待线程的数量,但可以给出各方(cl.getCount()),CyclicBarrier 可以给出没有等待线程 cb.getNumberWaiting(),以及各方(cb.getParties())
工作职责:countdownlatch 中的工作线程需要做countdown(),await() 由一个主线程完成。在 cyclicBarrier 工作线程和主线程中,所有线程都只相互等待()。
Reuse : CyclicBarrier 可以重复使用。cb.await() 适用于新线程,例如 t1、t2 和 main。对新线程 t3、t4 和 main 的第二次调用 cb.await() 也有效。Main 将在两个调用中等待,即系统在屏障退出后自动在内部重置计数(或 reset())。CountDownLatch 不能重复使用。- cl.await() 适用于新线程,例如 t1、t2 和 main。主线程等待 t1、t2 完成。但是对于第二个 cl.await() 调用新线程 t3,t4 和 main 不会等待。一旦集合中的所有线程都越过了障碍点,Phaser 对象可以再次被重新使用。
After Finish Events : 创建时,CountDownLatch 中不能给出完成事件,但可以在 CyclicBarrier 中给出。
我的班级{
static class MyThread implements Runnable
{
long waitTime;
CyclicBarrier cyclicBarrier;
CountDownLatch countdownlatch;
Phaser phaser;
MyThread( long waitTime, CyclicBarrier cyclicBarrier, CountDownLatch countdownlatch, Phaser phaser){
this.waitTime = waitTime;
this.cyclicBarrier = cyclicBarrier;
this.countdownlatch = countdownlatch;
this.phaser = phaser;
this.phaser.register(); //Note additional step here
}
@Override
public void run() {
try {
Thread.sleep(waitTime);
// Diff 4 -----> countdownlatch worker threads need to do countdown and await is done by one single main thread
//, cyclicBarrier worker threads await on each other
countdownlatch.countDown();
cyclicBarrier.await();
phaser.arriveAndAwaitAdvance();
System.out.println("cyclicBarrier :: " +
", name :: " + Thread.currentThread().getName()
+ ", parties :: " + cyclicBarrier.getParties()
+ ", waiting :: "+ cyclicBarrier.getNumberWaiting());
System.out.println("countdownlatch :: " +
"name :: " + Thread.currentThread().getName() +
", parties :: "+countdownlatch.getCount() +
", waiting :: " + "No method!!" );
System.out.println("phaser :: " +
"name :: " + Thread.currentThread().getName() +
", parties :: "+phaser.getRegisteredParties() +
", phase :: " + phaser.getPhase());
phaser.arriveAndAwaitAdvance();
System.out.println("phaser :: " +
"name :: " + Thread.currentThread().getName() +
", parties :: "+phaser.getRegisteredParties() +
", phase :: " + phaser.getPhase());
phaser.arriveAndAwaitAdvance();
System.out.println("phaser :: " +
"name :: " + Thread.currentThread().getName() +
", parties :: "+phaser.getRegisteredParties() +
", phase :: " + phaser.getPhase());
phaser.arriveAndDeregister();
if (phaser.isTerminated()) {
System.out.println("Phaser is terminated");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static class MyCBFinishEvent implements Runnable{
public void run() {
System.out.println("All threads have reached common barrier point "
+ ", CyclicBarrrierFinishEvent has been triggered");
System.out.println("You can update shared variables if any");
}
}
public static void main(String [] args) throws InterruptedException, BrokenBarrierException{
//Diff 1 ----- > No finish event can be given in CountDownLatch
//Diff 5 ------> CyclicBarrier no of worker threads includes main thread,
//CountDownLatch is just how many threads, the main waiting thread is not included in count.
CyclicBarrier cb = new CyclicBarrier(3, new MyCBFinishEvent());
CountDownLatch cl = new CountDownLatch(2);
Phaser ph = new Phaser();
//Diff 2 ----> CountDownLatch cant give num of waiting threads, CyclicBarrier can getNumberWaiting threads
System.out.println("Start CyclicBarrier - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
System.out.println("Start CountDownLatch - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );
Runnable t1 = new Thread(new MyThread( 10000, cb, cl, ph));
Runnable t2 = new Thread(new MyThread( 5000, cb, cl,ph));
Thread tt1 = new Thread(t1, "t1");
Thread tt2 = new Thread(t2, "t2");
tt1.start();
tt2.start();
//Diff 6 ---- > await meaning Main waits for t1 and t2 to complete,
//CyclicBarrier all are equal. each thread including main thread, if it wants to wait has to do await.
//CountDownLatch concept of waiting and workers. main thread await waits till other worker threads make count to 0.
cb.await();
cl.await();
System.out.println("End CyclicBarrier call 1 - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
System.out.println("End CountDownLatch call 1 - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );
System.out.println("main start created t3, t4 - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );
Runnable t3 = new Thread(new MyThread( 6000, cb, cl,ph));
Runnable t4 = new Thread(new MyThread( 100, cb, cl,ph));
Thread tt3 = new Thread(t3, "t3");
Thread tt4 = new Thread(t4, "t4");
tt3.start();
tt4.start();
//Diff -3 ----->
//CyclicBarrier - can be reused, main thread waited for t3, t4 to complete.
//CountDownLatch - for first cl.await(), main waited... second cl.await() call main did not wait!!!
cb.await();
cl.await();
System.out.println("End main - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
System.out.println("end main parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );
}
}
CountDownLatch:“现实生活中最好的例子是——种族”
(任何类型的比赛,例如:赛马或自行车比赛或汽车比赛等,)
赛马也是可以使用CountDownLatch
.
我们使用两个CountDownLatch
来开始和结束比赛。
CountDownLatch start = new CountDownLatch(1); // Start signal - will be always 1 only once All Horses Reach at Starting Point signal for START RACE.
CountDownLatch finish = new CountDownLatch(horses_Count); // Number of horses participating in Race nothing but Threads/Horses Count.
马 - 只不过是线程。
start.await() :
每匹马/线程一旦到达起始门/起点,它将等待其他马/线程到达起始门/起点无非是-start.await();
start.countDown() :一旦所有马匹/线程到达起始门/起点,我们发出开始比赛的信号只不过是 -start.countDown();
当start.countDown();
被调用时,它将notifyAll()
让所有等待的马匹/线程开始比赛。
finish.await() :一旦所有马匹/线程开始比赛,主线程将等待所有马匹/线程完成或完成 -finish.await();
finish.countDown():一旦每匹马/线程完成 Race 将减少计数一旦最后一匹马/线程减少计数为零然后 Race Completed/Finishedfinish.countDown();
当finish.countDown();
计数变为零时调用它将notify()
等待主线程 - 所有马/线程完成/完成了比赛。