173

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

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

对我来说,两者似乎是平等的,但我相信还有更多。

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

两者之间还有其他区别吗?有人想
在哪里重置倒计时的值?use cases

4

14 回答 14

145

还有一个区别。

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

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

“你为什么要那样做?”,你可能会说。想象一下,您正在使用由其他人编写的执行回调的神秘 API。您希望您的一个线程等到某个回调被多次调用。您不知道回调将在哪些线程上被调用。在这种情况下, aCountDownLatch是完美的,而我想不出任何方法来使用 a 来实现它CyclicBarrier(实际上,我可以,但它涉及超时......哎呀!)。

我只希望CountDownLatch可以重置!

于 2011-03-01T04:53:33.413 回答
144

一个主要区别是CyclicBarrier接受一个(可选的)可运行任务,一旦满足通用屏障条件,该任务就会运行。

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

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

于 2010-11-12T20:37:32.530 回答
48

还没有人提到的一点是,在 a 中CyclicBarrier,如果一个线程有问题(超时、中断......),所有其他已经到达的线程都会await()得到一个异常。请参阅 Javadoc:

CyclicBarrier 对失败的同步尝试使用全有或全无中断模型:如果一个线程由于中断、故障或超时而过早离开屏障点,则在该屏障点等待的所有其他线程也将通过 BrokenBarrierException(或 InterruptedException如果他们也几乎同时被打断)。

于 2012-10-07T16:03:02.503 回答
25

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

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

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

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

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

于 2014-07-02T06:33:50.087 回答
14

主要区别记录在 CountdownLatch 的 Javadocs 中。即:

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

1.6 Javadoc

于 2010-11-12T20:44:11.653 回答
12

CountDownLatch 用于一次性同步。在使用 CountDownLatch 时,任何线程都可以调用 countDown() 任意次数。由于其他未阻塞线程对 countDown() 的调用,调用 await() 的线程将被阻塞,直到计数达到零。CountDownLatch的javadoc状态:

由于调用了 countDown() 方法,await 方法会一直阻塞,直到当前计数达到零,然后释放所有等待的线程,并且任何后续的 await 调用都会立即返回。...

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

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

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

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

于 2012-04-30T02:02:22.750 回答
12

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

为了说明循环障碍的行为,我制作了一些示例代码。一旦障碍物倾倒,它就会自动重置,以便可以再次使用(因此它是“循环的”)。运行程序时,观察打印输出“Let's play”只有在障碍物倾斜后才会触发。

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();
        }
    }
}
于 2014-04-12T06:24:56.657 回答
12

当我研究闩锁和循环障碍时,我想到了这个比喻。 循环障碍:想象一家公司有一个会议室。为了开始会议,必须有一定数量的与会者参加会议(使其正式)。以下是普通会议参加者(员工)的代码

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

员工加入会议,等待其他人来开始会议。如果会议被取消,他也会退出:) 然后我们有老板不喜欢等待其他人出现,如果他失去了他的病人,他会取消会议。

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();
于 2017-08-09T11:31:12.977 回答
7

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

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的内部类 subclasses AQS, whileCyclicBarrier使用ReentrantLock(我怀疑它可能是其他方式,或者两者都可以使用 AQS 或两者都使用 Lock - 没有任何性能效率损失)

于 2017-01-23T21:56:55.883 回答
5

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

于 2015-10-29T17:21:07.197 回答
5

CountDownLatch中,主线程等待其他线程完成它们的执行。在CyclicBarrier中,工作线程相互等待完成它们的执行。

一旦计数达到零并且锁存器打开,您就不能重用相同的CountDownLatch实例,另一方面,可以通过重置屏障来重用CyclicBarrier ,一旦屏障被破坏。

于 2016-05-14T18:38:13.223 回答
4

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

于 2011-12-14T14:58:06.053 回答
1

CountDownLatch 是对任何事物的倒计时;CyclicBarrier 是线程的倒计时

假设有 5 个 worker 线程和 1 个 shipper 线程,当 worker 生产 100 件商品时,shipper 会将它们发货。

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

对于 CyclicBarrier,计数器只能在工人身上

如果一个工人陷入无限睡眠,在物品上使用 CountDownLatch,Shipper 可以发货;但是,使用 CyclicBarrier,永远无法调用 Shipper

于 2018-10-18T03:12:21.117 回答
0

@Kevin Lee 和@Jon 我尝试了带有可选 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.
于 2018-11-06T14:22:17.120 回答