Java并发性:倒数锁存与循环障碍

我正在阅读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一样。

还有另一个区别

在使用CyclicBarrier ,假设您指定了触发屏障的等待线程的数量。 如果指定5,则必须至less有5个线程才能调用await()

使用CountDownLatch ,可以指定调用countDown()的次数,这将导致所有等待的线程被释放。 这意味着你可以只用一个线程来使用CountDownLatch

“你为什么要那样做?”你可能会说。 想象一下,您正在使用由其他人执行callback的神秘API。 你希望你的一个线程等待,直到某个callback被多次调用。 您不知道将调用哪个线程的callback。 在这种情况下,一个CountDownLatch是完美的,但我想不出任何方式来实现这个使用CyclicBarrier (其实,我可以,但它涉及超时… yuck!)。

我只希望CountDownLatch可以重置!

有一点,没有人提到过,在CyclicBarrier ,如果一个线程有问题(超时,中断…),所有其他已经到达await()的exception。 参见Javadoc:

CyclicBarrier对失败的同步尝试使用全或非破坏模型:如果线程由于中断,失败或超时而提前离开障碍点,则在该障碍点等待的所有其他线程也将通过BrokenBarrierException(或InterruptedException如果他们也是在大约同一时间中断的话)。

我认为JavaDoc已经明确地解释了这些差异。 大多数人知道CountDownLatch不能被重置,但是,CyclicBarrier可以。 但是这不是唯一的区别,或者CyclicBarrier可以被重命名为ResetbleCountDownLatch。 我们应该从JavaDoc中描述的他们的目标angular度来讲述差异

CountDownLatch:允许一个或多个线程等待,直到在其他线程中执行的一组操作完成的同步辅助。

CyclicBarrier:一种同步协助,它允许一组线程互相等待,以达到一个共同的障碍点。

在countDownLatch中,有一个或多个线程正在等待一组其他线程的完成。 在这种情况下,有两种types的线程,一种types在等待,另一种types在做任务,在完成任务之后,他们可能正在等待或者只是终止。

在CyclicBarrier中,只有一种types的线程,它们相互等待,它们是相等的。

主要区别在于CountdownLatch的Javadocs。 即:

CountDownLatch用给定的计数初始化。 await方法阻塞,直到当前计数由于调用countDown()方法而达到零,在此之后所有等待的线程被释放,并且任何后续的调用立即返回。 这是一次性现象 – 计数不能被重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier。

来源1.6 Javadoc

这个问题已经得到了充分的回答,但我想我可以通过发布一些代码来增加一点价值。

为了说明循环障碍的行为,我做了一些示例代码。 一旦障碍倾斜,它会自动重置,以便它可以再次使用(因此它是“循环”)。 当您运行该程序时,观察打印输出“让我们玩”只有在障碍倾斜后才会触发。

 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(); } } } 

CountDownLatch用于一次性同步。 在使用CountDownLatch时,任何线程都可以根据需要多次调用countDown()。 调用await()的线程将被阻塞,直到count由于其他未阻塞的线程对countDown()的调用而达到零。 CountDownLatch的javadoc指出:

await方法阻塞,直到当前计数由于调用countDown()方法而达到零,在此之后所有等待的线程被释放,并且任何后续的调用立即返回。 …

另一个典型的用法是将一个问题分成N个部分,每个部分用一个Runnable来描述,这个Runnable执行这个部分,然后把所有的Runnables排队到一个Executor。 当所有子部分完成时,协调线程将能够通过等待。 (当线程必须以这种方式重复倒计数,而不是使用CyclicBarrier。)

相反,循环屏障用于多个同步点,例如,如果一组线程正在运行循环/分阶段计算,并且在开始下一个迭代/阶段之前需要同步。 根据CyclicBarrier的javadoc :

这个屏障被称为循环的,因为它可以在等待的线程被释放之后重新使用。

与CountDownLatch不同的是,每个await()调用都属于某个阶段,并可能导致线程阻塞,直到属于该阶段的所有方都调用了await()。 CyclicBarrier不支持显式的countDown()操作。

在CyclicBarrier的情况下,只要ALL子线程开始调用barrier.await(),Runnable就会在Barrier中执行。 等待每个子线程的障碍需要不同的时间才能完成,并且同时完成。

一个明显的区别是,只有N个线程可以等待N个CyclicBarrier在一个周期内释放。 但是可以在N的CountDownLatch上等待无限数量的线程。递减递减可以由一个线程N次或N个线程每次或每次组合完成。

简而言之 ,只是为了理解两者之间的关键function差异:

 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(); } } } 

当然,除了非阻塞,定时等待,诊断和上面答案中详细解释的所有内容之外。

然而,上面的类在function上是完全function和等同的,它们是对应的名称。

在不同的说明中, CountDownLatch的内部类是AQS子类,而CyclicBarrier使用的是ReentrantLock (我CyclicBarrier怀疑可能是其他方法,或者两者都可以使用AQS,或者都使用Lock),而不会损失性能效率)

CountDownLatch中 ,主线程等待其他线程完成执行。 在CyclicBarrier中 ,工作线程等待对方完成执行。

一旦计数达到零并且闩锁打开,则不能再次使用相同的CountDownLatch实例。另一方面,可以通过重置障碍来重复使用CyclicBarrier ,一旦障碍被破坏。

当我正在研究闩锁和周期性障碍时,我想出了这个比喻。 周期性障碍 :想象一个公司有一个会议室。 为了开始会议,有一定数量的与会者必须来参加会议(使其正式)。 以下是常规会议参与者(员工)的代码

 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 <by chic band!>"); } } } 

员工参加会议,等待他人来开始会议。 如果会议被取消,他也会退出)那么我们就有了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 <by chic band!> 

还有另外一个局外线程(地震)取消了会议(呼叫重置方法)。 在这种情况下,所有等待的线程都被exception唤醒。

 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 <by chic band!> 

你也可以在会议室增加一个秘书,如果开会,她会logging每件事情,但她不是会议的一部分:

 class MeetingSecretary implements Runnable { @Override public void run() { System.out.println("preparing meeting documents"); System.out.println("taking notes ..."); } } 

locking :如果愤怒的老板想为公司客户举办展览会,每件事情都需要做好准备(资源)。 我们提供了一个待办事项列表,每个工作人员(线程)的工作和我们检查待办事项列表(一些工作人员做绘画,其他人准备音响系统…)。 当待办事项列表中的所有项目都已完成(提供资源)时,我们可以向客户开门。

 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();