CountDownLatch和CyclicBarrier的实际应用示例

41

我们的一位培训师在解释CountDownLatch和CyclicBarrier的区别时提供了一个示例。

CountDownLatch:假设有一块石头需要10个人才能举起,因此你需要等待所有10个人到齐,然后才能举起这块石头。

CyclicBarrier:如果你要去野餐,并且需要先在某个公共地点集合,然后大家一起开始旅程。

如果有人同意这些评论,请给我一些细节。

我已经阅读了这些类的sun API。但是我需要更多的解释。


另一个你可能会觉得有用的链接是http://adnanfaizan.blogspot.in/2013/10/countdownlatch-tutorial-class-of-java.html - Mohammad Adnan
@Sunny 这是一个旧问题,但对我来说是新的。你能否回答两个问题:(1) 你的循环屏障示例(野餐)在这里是否正确?(2) 在这个循环屏障示例中,如果有10个人去野餐,他们需要在某个共同点相遇,然后再开始他们的旅程。在到达俱乐部(或目的地)之前,所有线程是否可以有多个共同点相遇? - Pradeep Singh
@PradeepSingh 1)野餐的例子是正确的。2)您可以有多个公共点,通过不同实例的循环屏障可以实现这一点。 - Sunny Gupta
谢谢 Sunny ^_^ - Pradeep Singh
1
@SunnyGupta,你应该总是说一下你在评论中链接的视频是你自己的。 - Prashant
显示剩余2条评论
13个回答

81

在一个假想的剧院中:

  • 如果只允许一个人观看戏剧,则称其为Mutex
  • 如果允许N个人观看戏剧,如果有人在演出期间离开剧院,则可以允许其他人观看戏剧,则称其为Semaphore
  • 如果不允许任何人进入,直到每个人都离开剧院,则称其为CountDownLatch。这里每个人都有自由意志离开剧院。
  • 如果演出不会开始,直到每个人都进入剧院,则称其为CyclicBarrier。这里表演者不能开始演出,直到所有人进入并抢占座位。一旦演出结束,下一场演出将应用相同的屏障。

这里的一个人是一个线程,一个戏剧是一个资源


4
这是一个非常棒的学院级别的例子!非常感谢! - Netherwire
@Netherwire 同意,这太棒了! :D - varun
非常好的例子,我可以很容易地记住它...谢谢。 - Bhuppi
非常好的例子。 - Manoj Kumar Julakanti
解释记住事情的好方法 - Avinesh

44

关键差别在于 CountDownLatch 将线程分为等待者和到达者,而使用 CyclicBarrier 的所有线程都扮演两个角色。

  • 使用门闩,等待者等待最后一个到达的线程到来,但那些到达的线程不会自己等待。
  • 使用屏障,所有线程到达后等待最后一个线程到达。

你的门闩示例说明了全部十个人必须一起等待举起石头。这并非如此。更好的现实示例是考试监考员耐心地等待每个学生交卷。学生完成考试后就不再等待,可以离开。最后一个学生交卷(或时间到),监考员停止等待并带着试卷离开。


使用锁存器,服务员等待最后一个到达的线程到达,但是这些到达的线程本身不会等待。根据我的理解,这不应该是这种情况,因为主线程总是等待所有线程倒计时到0。请解释一下。 - Sunny Gupta
这个例子应该是一个协调员检查是否有10个人可以一起搬动石头。我说得对吗? - Sunny Gupta
6
只有调用await方法的线程会等待CountDownLatch计数器归零,调用countDown方法的线程不会被阻塞。 - David Harkness

22

真实世界的例子
我发现所有答案都缺少一个真实的例子,就是这些类在软件领域中如何使用。

  1. CountDownLatch 一个多线程下载管理器。 下载管理器将启动多个线程同时下载文件的每个部分(前提是服务器支持多线程下载)。这里,每个线程将调用已实例化闩锁的倒计时方法。所有线程执行完成后,与倒计时闩锁相关联的线程将把不同部分中找到的数据整合成一个文件。

  2. CyclicBarrier 与上面的情况相同,但假设文件是从P2P下载的。同样地有多个线程下载各个部分。但是这里,假设您希望在特定时间间隔后对下载的部分进行完整性检查。这时循环屏障起着重要作用。每个时间间隔后,每个线程都会等待在屏障处,以便与循环屏障相关联的线程可以进行完整性检查。由于循环屏障,可以多次进行完整性检查。

如果有任何不当之处,请指出。


5

使用场景1:假设您将一个大任务拆分成10个小任务,每个任务都是一个线程。在完成作业之前,您必须等待那些线程中的10个任务全部结束。

因此,主要的作业启动线程初始化了一个CountDownLatch计数器,用于计算使用的线程数量,它将任务分配给线程并等待使用await方法时latch计数降为零。每个执行者线程将在其任务结束时调用countDown。最后,当所有线程都已完成时,主线程将被唤醒,从而认为整个作业已经完成。这种情况下使用了CountDownLatch javadoc中描述的doneSignal标志。

使用场景2:假设您将一个大任务拆分成n * m个任务,分布在n个线程上。m对应于矩阵行,您需要计算每一行的总数。在这种情况下,必须在每个任务结束后同步线程,以便处理该行的总数。在这种情况下,使用初始化为线程数n的CyclicBarrier来等待每行计算的结束(实际上为m次)。

为了比较两者,CountDownLatch只能被使用1次,而CyclicBarrier可以根据算法需要为一组线程提供多个同步点。


4

CountDownLatch: 如果我们想让所有线程都执行

某些操作并执行倒计时

以便等待中的其他线程可以继续进行,我们可以使用倒计时锁。在这种情况下,实际执行倒计时的所有先前线程都可以继续进行,但不能保证latch.countdown()之后处理的行将在等待其他线程到达latch.countdown()之后进行,但保证其他等待的线程只有在latch.await()到达零之后才会继续进行。

CyclicBarrier: 如果我们想让所有线程都

在一个公共点上等待 + 执行某些操作

(每个await调用将减少线程继续执行的等待时间)

可以通过一次调用latch.countdown()后由所有线程调用latch.await()来使用CountDownLatch实现CyclicBarrier的功能。

但是,您无法重置/重复使用倒计时锁。

我使用CyclicBarrier的最佳示例是初始化多个缓存(由多个线程加热),然后启动进一步处理,并且我希望在同步中重新初始化其他缓存。


4

理论区别:

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

一旦计数达到零且闩锁打开,您无法重复使用相同的CountDownLatch实例。另一方面,可以通过重置障碍来重复使用CyclicBarrier,一旦障碍被打破。

现实生活示例:--

CountDownLatch:考虑一个IT行业场景,经理将模块分配给开发团队(A和B),并希望只有当两个团队都完成任务时才将其分配给QA团队进行测试。

这里经理线程充当主线程,开发团队充当工作线程。经理线程等待开发团队线程完成任务。

CyclicBarrier:考虑相同的IT行业场景,经理将模块分配给开发团队(A和B)。他休假了,并要求两个团队互相等待对方完成各自的任务,一旦两个团队都完成了任务,就将其分配给QA团队进行测试。

这里经理线程充当主线程,开发团队充当工作线程。开发团队线程在完成任务后等待其他开发团队线程。


4
一个 CyclicBarrier 可以重复使用,所以它更像是一个赛车旅行,在继续下一段旅程之前,大家都会在一个路标处相遇。

这里讨论了一个CountDownLatch的例子:[https://dev59.com/3VDTa4cB1Zd3GeqPGRYV#3588523]。 - trashgod

2

对于CyclicBarrier,我能想到一个实时的例子:假设有一群旅游者在一辆小巴上。一天内需要参观多个景点。司机知道旅游者的数量。当到达第一个景点时,所有旅游者下车并在不同的时间返回;然而,小巴和旅游者必须等待直到所有旅游者都回来。一旦所有人都回来,司机才会前往下一个景点,重复相同的过程。在这里,CyclicBarrier初始化为旅游者的数量。每个旅游者就像一个线程,在返回后,他们调用CyclicBarrier await(),以便等待其他旅游者都回来。请让我知道你的想法。


2
CyclicBarrier:在Ludo游戏APP中,我们不能开始四个玩家的游戏,直到所有玩家都加入了游戏。假设所有四个玩家都是单独的线程。在这种情况下,一个玩家(线程)必须等待所有其他线程(玩家)加入游戏。每个线程在加入游戏后都会调用await()方法。只有在定义数量的线程调用await()方法后,它们才能继续执行。即所有玩家都已加入游戏。 CountDownLatch:在一家IT公司中,假设正在进行一个由四名成员组成的团队的调查,并且经理需要向高层管理层提交所有员工的反馈意见。在这种情况下,所有四名员工都是线程,他们将提供自己的反馈并调用countDown()方法。除非所有员工都没有提交反馈或者已经到达截止日期,否则经理线程必须通过调用await()方法等待。在从员工那里获得反馈后,经理可以将反馈提交给高层管理层。在CountDownLatch的情况下,线程不需要互相等待,它们可以在调用countDown()方法后继续执行。即员工可以继续编写程序或在提交反馈后去吃午饭。

点击此处获取更多详细信息


1

CountDownLatch vs CyclicBarrier

概念

我们有一条过山车,有一个等待队列。在任何时候,只有一个人可以进入过山车车厢,并坐下。每个车厢都有9个座位(3x3座位),可以容纳9个人。当其中3个座位被占满后,这3个座位的安全杆便会关闭。当所有车厢(我们总共有3个车厢,所以有3x9个座位,即27个座位)都被占满时,过山车就会出发。

CountDownLatch和CyclicBarrier之间的区别

CyclicBarrier

CyclicBarrier可用于为每组3个座位关闭安全杆。 在CyclicBarrier中,子线程将等待所有3个座位被占满,然后才能继续并单独关闭每个线程的安全杆。由于是循环的,因此可以在每组3个座位上重复使用。

CountDownLatch

CountDownLatch可以用于在所有座位被占满后启动过山车。一种类似主线程的线程将等待所有座位被占满。

代码示例

package Concurrency;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchVsCyclicBarrier
{
    private final ExecutorService es = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private final Integer numberOfSeats = 27;
    private final Integer numberOfSeatsInOneRow = 3;

    private final CountDownLatch latch = new CountDownLatch(numberOfSeats);
    private final CyclicBarrier barrier = new CyclicBarrier(numberOfSeatsInOneRow);

    public void run() throws Exception
    {
        for (int person = 1; person < 28; person++) {
            int finalPerson = person;
            es.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Waiting for person " + finalPerson + " to take a seat");
                    try { Thread.sleep(1500); } catch(InterruptedException ignored) {}
                    System.out.println("Waiting to close safety bar for person " + finalPerson);
                    try { barrier.await(); } catch(Exception ignored) {}
                    System.out.println("Closing the safety bar for person " + finalPerson);
                    latch.countDown();
                }
            });
            Thread.sleep(100);
        }

        latch.await(); // Wait for all 27 seats to be taken
        System.out.println("Start the rollercoaster");
        es.shutdown();
    }

    public static void main(String[] args) throws Exception
    {
        new CountDownLatchVsCyclicBarrier().run();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接