Java并发:倒计时锁存器与循环屏障

时间:2010-11-12 20:26:41

标签: java concurrency countdownlatch cyclicbarrier

我正在阅读java.util.concurrent API,并发现

  • CountDownLatch:允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
  • CyclicBarrier:一种同步辅助工具,允许一组线程全部等待彼此到达公共障碍点。

对我而言,两者似乎都是平等的,但我相信还有更多的东西。

例如,在CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier中。

两者之间还有其他区别吗? 有人想要重置倒计时值的use cases是什么?

14 个答案:

答案 0 :(得分:123)

一个主要区别是CyclicBarrier采用(可选)Runnable任务,该任务在满足公共屏障条件后运行。

它还允许您获取在屏障处等待的客户端数量以及触发屏障所需的数量。一旦触发,屏障就会重置并可以再次使用。

对于简单的用例 - 服务启动等... CountdownLatch很好。 CyclicBarrier对于更复杂的协调任务非常有用。这种事情的一个例子是并行计算 - 计算中涉及多个子任务 - 类似于MapReduce

答案 1 :(得分:115)

还有另一个不同之处。

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

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

"你为什么这样做?",你可能会说。想象一下,您正在使用由执行回调的其他人编码的神秘API。您希望其中一个线程等待多次调用某个回调。您不知道将调用哪个回调函数。在这种情况下,CountDownLatch是完美的,而我无法想到使用CyclicBarrier实现这一点(实际上,我可以,但它涉及超时......哎呀!)

我只希望CountDownLatch可以重置!

答案 2 :(得分:38)

有人提到的一点是,在CyclicBarrier中,如果某个线程出现问题(超时,中断......),则所有其他已达到await()的人都会遇到异常。见Javadoc:

  

CyclicBarrier使用全部或全部破坏模型进行失败的同步尝试:如果线程因中断,失败或超时而过早地离开障碍点,则在该障碍点等待的所有其他线程也将通过BrokenBarrierException异常地离开(如果它们在大约同一时间也被中断,则为InterruptedException。)

答案 3 :(得分:20)

我认为JavaDoc明确地解释了这些差异。 大多数人都知道CountDownLatch无法重置,但是,CyclicBarrier可以。但这不是唯一的区别,或者CyclicBarrier可以重命名为ResetbleCountDownLatch。 我们应该从JavaDoc

中描述的目标的角度来区分它们

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

CyclicBarrier:一种同步辅助工具,允许一组线程等待彼此到达共同的障碍点。

在countDownLatch中,有一个或多个线程正在等待一组其他线程完成。在这种情况下,有两种类型的线程,一种类型正在等待,另一种类型正在做某事,在完成任务后,它们可能正在等待或刚刚终止。

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

答案 4 :(得分:14)

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

  

使用a初始化CountDownLatch   给定数量。 await方法块   直到当前计数达到零   由于countDown()的调用   方法,之后全部等待   线程被释放,任何   随后的等待返回调用   立即。这是一次性的   现象 - 计数不可能   重启。如果你需要一个版本   重置计数,考虑使用a   的CyclicBarrier。

来源1.6 Javadoc

答案 5 :(得分:12)

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

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

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

答案 6 :(得分:10)

CountDownLatch用于一次性同步。使用CountDownLatch时,允许任何线程多次调用countDown()。调用await()的线程被阻塞,直到计数达到零,因为其他未阻塞的线程调用了countDown()。 javadoc for CountDownLatch州:

  

await方法阻塞,直到当前计数达到零为止   调用countDown()方法,之后所有等待的线程   被释放,随后的任何调用等待返回   立即。   ...

     

另一种典型用法是将问题分成N个部分,   用执行该部分的Runnable描述每个部分   向下计数锁存器,并将所有Runnables排队到Executor。   当所有子部件都完成后,协调线程就可以了   通过等待。 (当线程必须重复倒计时   这样,改为使用CyclicBarrier。)

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

  

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

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

答案 7 :(得分:7)

当我研究锁存器和循环障碍时,我提出了这个隐喻。 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 <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!>

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

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!>

您还可以在会议室添加秘书,如果举行会议,她会记录每件事,但她不参加会议:

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

答案 8 :(得分:6)

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

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或两者都使用Lock - 而不会降低性能效率)

答案 9 :(得分:4)

在CyclicBarrier的情况下,只要所有子线程开始调用barrier.await(),就会在Barrier中执行Runnable。每个子线程中的barrier.await将花费不同的时间来完成,并且它们都在同一时间完成。

答案 10 :(得分:4)

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

答案 11 :(得分:3)

CountDownLatch 中,主线程等待其他线程完成其执行。在 CyclicBarrier 中,工作线程彼此等待以完成其执行。

一旦计数达到零并且闩锁打开,您就不能重复使用相同的 CountDownLatch 实例,另一方面, CyclicBarrier 可以通过重置屏障重新使用,一旦障碍被打破

答案 12 :(得分:1)

CountDownLatch是任何东西的倒数; CyclicBarrier仅适用于线程倒计时

假设有5个工作线程和1个托运人线程,当工作人员生产100件物品时,托运人将其运出。

对于CountDownLatch,计数器可以位于工作人员或项目上

对于CyclicBarrier,计数器只能在工作人员上使用

如果工人无限睡眠,且物品上装有CountDownLatch,则托运人可以托运;但是,使用CyclicBarrier,永远无法调用Shipper

答案 13 :(得分:0)

@Kevin Lee和@Jon我尝试了带有Optional Runnable的CyclicBarrier。看起来它在CyclicBarrier的开头和之后运行。这是代码和输出

静态CyclicBarrier屏障;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

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

输出

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.