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


160

我在阅读java.util.concurrent API时发现

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

在我看来,两者似乎是平等的,但我相信还有更多的东西。

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

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


12
闩锁用于等待事件;障碍是等待其他线程。-实践中的Java并发,B.Goetz等。
user2418306 '16

Answers:


137

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

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

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


6
“它还使您能够获得在障碍处等待的客户端数量以及触发障碍所需的数量。触发后,障碍将被重置并可以再次使用。” 我真的很喜欢这一点。我读过几篇文章建议CyclicBarrier是循环的,因为您调用了reset()方法。没错,但是他们很少提及的是,屏障被触发后会自动重置。我将发布一些示例代码来说明这一点。
Kevin Lee

@Kevin Lee感谢您:“屏障一旦触发就会自动重置。” 因此无需在代码中调用reset()。
超新星

134

还有另一个区别。

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

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

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

我只希望CountDownLatch可以重新设置!


10
我认为这是更好地显示理论差异的答案。可以通过多次调用方法来破坏锁存器,而屏障需要精确数量的线程才能等待()。
flagg19

43
对-那是主要区别:CountDownLatch-> NumberOfCalls,CyclicBarrier-> NumberOfThreads
Ivan Voroshilin 2014年

1
我同意对它CountDownLatch进行重置非常好-我用来实现粗略的等待通知的一种解决方法是,CountDownLatch当进入受保护的代码块时(锁存器达到零时)立即立即进行更新。当然,这并非在所有情况/范围内都适用,但是我认为值得注意的是,这是在金发姑娘情况下的一种选择。
Ephemera 2015年

2
关于此主题的最佳答案之一。Java Concurrency in Practice-说同样的话:Latches are for waiting for events; barriers are for waiting for other threads.。了解这两者之间的区别的主要要点。
Rahul Dev Mishra

Java 8文档说:“初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程已完成某些操作或某些操作已完成N次。” 在我看来:CountDownLatch-> NumberOfCalls或CountDownLatch-> NumberOfThreads
凌晨

41

尚未有人提到的一点是,在中CyclicBarrier,如果线程有问题(超时,中断...),则所有其他已达到await()异常的事件。参见Javadoc:

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


22

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

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

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

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

在CyclicBarrier中,只有一种类型的线程,它们彼此等待,它们相等。


1
“在CyclicBarrier中,只有一种类型的线程” ...在其他线程调用.await()之前,它们在“等待角色”中是相等的,但它们可能“在执行任务时不相等”。同样,它们都必须是同一类型或不同类型的绝对不同的线程实例(!),而在CountDownLatch中,同一线程可能会调用countDown()并影响结果。
弗拉基米尔·纳博科夫

我同意CountDownLatch本质上需要两个角色:一个用于countDown的客户端和一个用于等待的客户端。另一方面,CyclicBarrier客户端可以使用await方法正常运行。
isaolmez

14

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

CountDownLatch用给定的计数初始化。由于countCount()方法的调用,直到当前计数达到零为止,await方法将阻塞,此后所有释放线程都将被释放,并且任何随后的await调用将立即返回。这是一种一次性现象-无法重置计数。如果需要用于重置计数的版本,请考虑使用CyclicBarrier。

源代码1.6 Javadoc


4
如果它们的区别只是可以重置还是不能重置,则CyclicBarrier可能更好地命名为ResetableCountDownLatch,由于该区别,它更有意义。
James.Xu

12

CountDownLatch用于一次性同步。在使用CountDownLatch时,任何线程都可以随意调用countDown()多次。调用await()的线程被阻塞,直到计数达到零为止,这是因为其他未阻塞的线程对countDown()的调用。CountDownLatchJavadoc状态为:

由于countCount()方法的调用,当前的await方法将阻塞直到当前计数达到零为止,此后,所有等待线程都将被释放,并且所有随后的await调用将立即返回。...

另一个典型用法是将问题分为N个部分,用Runnable描述每个部分,该Runnable执行该部分并在锁存器中递减计数,并将所有Runnable排队给执行程序。当所有子部分都完成后,协调线程将能够通过等待。(当线程必须以此方式反复递减计数时,请使用CyclicBarrier。)

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

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

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


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

8

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

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

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的内部类子类AQSCyclicBarrier使用的同时ReentrantLock(我怀疑这可能是另一种方式,或者两者都可以使用AQS或都使用Lock,而不会降低性能)


5

一个明显的区别是,只有N个线程可以在一个周期的N个CyclicBarrier上等待释放。但是,CountDownLatch为N时可以等待无限数量的线程。向下计数递减可以由一个线程执行N次,或者由N个线程执行,每次或一次。


4

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


4

CountDownLatch中,主线程等待其他线程完成其执行。在CyclicBarrier中,辅助线程互相等待以完成其执行。

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


它不必是主线程。它可以是任何创建CountDownLatch并与其他非主线程共享的线程。
Aniket Thakur

1

CountDownLatch是任何东西的倒数;CyclicBarrier仅针对线程进行倒计时

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

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

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

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


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.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.