我正在阅读java.util.concurrent API,并发现了
CountDownLatch
:允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助.
CyclicBarrier
:一种同步辅助,允许一组线程全部等待彼此到达公共障碍点.
对我来说,两者似乎都是平等的,但我相信还有更多的东西.
例如,在CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier
.
这两者之间还有其他区别吗?
有什么use cases
地方会有人想重置倒计时的价值?
一个主要区别是CyclicBarrier采用(可选)Runnable任务,该任务在满足公共屏障条件时运行.
它还允许您获取在屏障处等待的客户端数量以及触发屏障所需的数量.一旦触发,屏障就会重置并可以再次使用.
对于简单的用例 - 服务启动等... CountdownLatch很好.CyclicBarrier对于更复杂的协调任务非常有用.这种事情的一个例子是并行计算 - 计算中涉及多个子任务 - 类似于MapReduce.
还有另外一个区别.
使用a时CyclicBarrier
,假设您指定触发屏障的等待线程数.如果指定5,则必须至少有5个线程才能调用await()
.
使用a时CountDownLatch
,指定调用次数countDown()
将导致所有等待线程被释放.这意味着您只能使用一个CountDownLatch
线程.
"你为什么要那样做?",你可能会说.想象一下,您正在使用由执行回调的其他人编码的神秘API.您希望其中一个线程等待多次调用某个回调.您不知道将调用哪个回调函数.在这种情况下,a CountDownLatch
是完美的,而我想不出任何方法来实现这个CyclicBarrier
(实际上,我可以,但它涉及超时......哎呀!).
我只是希望CountDownLatch
可以重置!
没有人提到的一点是,在a中CyclicBarrier
,如果一个线程有问题(超时,中断......),所有其他已经到达的例子await()
都会获得异常.见Javadoc:
CyclicBarrier使用全部或全部破坏模型进行失败的同步尝试:如果线程因中断,失败或超时而过早地离开障碍点,则在该障碍点等待的所有其他线程也将通过BrokenBarrierException(或InterruptedException)异常地离开如果他们也在大约同一时间被打断了).
我认为JavaDoc明确地解释了这些差异.大多数人都知道CountDownLatch无法重置,但是,CyclicBarrier可以.但这不是唯一的区别,或者CyclicBarrier可以重命名为ResetbleCountDownLatch.我们应该从JavaDoc中描述的目标的角度来区分它们
CountDownLatch:允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助.
CyclicBarrier:一种同步辅助工具,允许一组线程全部等待彼此到达公共障碍点.
在countDownLatch中,有一个或多个线程正在等待一组其他线程完成.在这种情况下,有两种类型的线程,一种类型正在等待,另一种类型正在做某事,在完成任务后,它们可能正在等待或刚刚终止.
在CyclicBarrier中,只有一种类型的线程,它们彼此等待,它们是相等的.
主要区别在于CountdownLatch的Javadocs.即:
使用给定计数初始化CountDownLatch.由于countDown()方法的调用,await方法阻塞直到当前计数达到零,之后释放所有等待的线程,并且任何后续的await调用立即返回.这是一次性现象 - 计数无法重置.如果您需要重置计数的版本,请考虑使用CyclicBarrier.
源1.6 Javadoc
CountDownLatch用于一次性同步.使用CountDownLatch时,允许任何线程多次调用countDown().调用await()的线程被阻塞,直到计数达到零,因为其他未阻塞的线程调用了countDown().CountDownLatch的javadoc指出:
由于countDown()方法的调用,await方法阻塞直到当前计数达到零,之后释放所有等待的线程,并且任何后续的await调用立即返回....
另一个典型的用法是将问题分成N个部分,用执行该部分的Runnable描述每个部分并对锁存器进行倒计时,并将所有Runnables排队到Executor.当所有子部件都完成后,协调线程将能够通过等待.(当线程必须以这种方式重复倒计时时,而是使用CyclicBarrier.)
相反,循环屏障用于多个同步点,例如,如果一组线程正在运行循环/分阶段计算并且需要在开始下一个迭代/阶段之前进行同步.根据CyclicBarrier的javadoc:
屏障称为循环,因为它可以在等待线程释放后重新使用.
与CountDownLatch不同,对await()的每次调用都属于某个阶段,并且可能导致线程阻塞,直到属于该阶段的所有各方都调用了await().CyclicBarrier不支持显式的countDown()操作.
这个问题已经得到了充分的回答,但我认为我可以通过发布一些代码来增加价值.
为了说明循环障碍的行为,我做了一些示例代码.一旦屏障倾斜,它就会自动重置,以便可以再次使用(因此它是"循环的").运行程序时,请注意只有在屏障倾斜后才会触发打印输出"让我们玩".
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierCycles { static CyclicBarrier barrier; public static void main(String[] args) throws InterruptedException { barrier = new CyclicBarrier(3); new Worker().start(); Thread.sleep(1000); new Worker().start(); Thread.sleep(1000); new Worker().start(); Thread.sleep(1000); System.out.println("Barrier automatically resets."); new Worker().start(); Thread.sleep(1000); new Worker().start(); Thread.sleep(1000); new Worker().start(); } } class Worker extends Thread { @Override public void run() { try { CyclicBarrierCycles.barrier.await(); System.out.println("Let's play."); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }
当我研究关于锁存器和循环障碍的时候,我想出了这个比喻. cyclicbarriers:想象一下公司有会议室.为了开始会议,一定数量的与会者必须参加会议(使其成为正式会议).以下是正常会议与会者(员工)的代码
class MeetingAtendee implements Runnable { CyclicBarrier myMeetingQuorumBarrier; public MeetingAtendee(CyclicBarrier myMileStoneBarrier) { this.myMeetingQuorumBarrier = myMileStoneBarrier; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + " i joined the meeting ..."); myMeetingQuorumBarrier.await(); System.out.println(Thread.currentThread().getName()+" finally meeting stared ..."); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { System.out.println("Meeting canceled! every body dance"); } } }
员工加入会议,等待其他人前来开会.如果会议被取消,他也会退出:)然后我们有了BOSS怎么不喜欢等待别人出现的剂量,如果他失去他的病人,他取消会议.
class MeetingAtendeeTheBoss implements Runnable { CyclicBarrier myMeetingQuorumBarrier; public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) { this.myMeetingQuorumBarrier = myMileStoneBarrier; } @Override public void run() { try { System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ..."); //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS); System.out.println(Thread.currentThread().getName()+" finally meeting stared ..."); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { System.out.println("what WHO canceled The meeting"); } catch (TimeoutException e) { System.out.println("These employees waste my time!!"); } } }
在正常的一天,员工来到会议等待其他人出现,如果有些与会者不来,他们必须无限期地等待!在一些特别的会议上,老板来了,他不想等.(5个人需要开始见面,但只有老板来,也是一个热情的员工)所以他取消了会议(愤怒)
CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5); Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum)); Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum)); atendeeThread.start(); atendeeThreadBoss.start();
输出:
//Thread-1I am THE BOSS - i joined the meeting ... // Thread-0 i joined the meeting ... // These employees waste my time!! // Meeting canceled! every body dance
还有另一种情况,其中另一个局外人线程(地震)取消会议(呼叫重置方法).在这种情况下,所有等待的线程都被异常唤醒.
class NaturalDisasters implements Runnable { CyclicBarrier someStupidMeetingAtendeeQuorum; public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) { this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum; } void earthQuakeHappening(){ System.out.println("earth quaking....."); someStupidMeetingAtendeeQuorum.reset(); } @Override public void run() { earthQuakeHappening(); } }
运行代码将导致有趣的输出:
// Thread-1I am THE BOSS - i joined the meeting ... // Thread-0 i joined the meeting ... // earth quaking..... // what WHO canceled The meeting // Meeting canceled! every body dance
您还可以在会议室添加秘书,如果举行会议,她将记录每件事,但她不参加会议:
class MeetingSecretary implements Runnable { @Override public void run() { System.out.println("preparing meeting documents"); System.out.println("taking notes ..."); } }
锁定:如果愤怒的老板想要为公司客户举办展览,那么每件事都需要做好准备(资源).我们提供了一份待办事项列表,每个工人(线程)都会为他的工作做好准备,我们会检查待办事项列表(一些工人正在绘画,其他工作人员正在制作音响系统......).当待办事项列表中的所有项目都已完成(提供资源)时,我们可以为客户打开大门.
public class Visitor implements Runnable{ CountDownLatch exhibitonDoorlatch = null; public Visitor (CountDownLatch latch) { exhibitonDoorlatch = latch; } public void run() { try { exhibitonDoorlatch .await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("customer visiting exebition"); } }
工人们如何准备展览:
class Worker implements Runnable { CountDownLatch myTodoItem = null; public Worker(CountDownLatch latch) { this.myTodoItem = latch; } public void run() { System.out.println("doing my part of job ..."); System.out.println("My work is done! remove it from todo list"); myTodoItem.countDown(); } } CountDownLatch preperationTodoList = new CountDownLatch(3); // exhibition preparation workers Worker electricalWorker = new Worker(preperationTodoList); Worker paintingWorker = new Worker(preperationTodoList); // Exhibition Visitors ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList); ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList); ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList); new Thread(electricalWorker).start(); new Thread(paintingWorker).start(); new Thread(exhibitionVisitorA).start(); new Thread(exhibitionVisitorB).start(); new Thread(exhibitionVisitorC).start();
简而言之,只是为了理解两者之间的关键功能差异:
public class CountDownLatch { private Object mutex = new Object(); private int count; public CountDownLatch(int count) { this.count = count; } public void await() throws InterruptedException { synchronized (mutex) { while (count > 0) { mutex.wait(); } } } public void countDown() { synchronized (mutex) { if (--count == 0) mutex.notifyAll(); } } }
和
public class CyclicBarrier { private Object mutex = new Object(); private int count; public CyclicBarrier(int count) { this.count = count; } public void await() throws InterruptedException { synchronized (mutex) { count--; while(count > 0) mutex.wait(); mutex.notifyAll(); } } }
当然,除了非阻塞,定时等待,诊断以及上述答案中详细说明的所有内容之外.
然而,上述类在所提供的功能中与其对应的名称完全相同并且是等同的.
在另一个注释,CountDownLatch
内部类的子类AQS
,虽然CyclicBarrier
使用ReentrantLock
(我怀疑它可能是其他方式或两者都可以使用AQS或两者都使用锁 - 没有任何性能效率损失)