CountDownLatch和CyclicBarrier的真实生活示例

时间:2012-04-14 18:30:16

标签: java concurrency conceptual

我们的一位培训师在解释CountDownLatch和CyclicBarrier之间的差异时给出了一个例子。

CountDownLatch:假设一块石头可以被10个人抬起,所以你将等待所有10个人来。然后只有你可以抬起石头。

CyclicBarrier:如果你要去野餐,你需要先在一个共同的地方见面,然后开始你的旅程。

如果有人同意这些意见,请告诉我一些细节。

我已经阅读过这两个类的sun API。但我还需要一些解释。

11 个答案:

答案 0 :(得分:37)

在一个假想的剧院里,

如果只有一个人允许观看比赛,则称为 Mutex 如果N人允许观看比赛,则称为信号量。如果有人在比赛期间离开剧院,则可以允许其他人观看比赛。
它被称为 CountDownLatch 如果没有人允许进入,直到每个人都离开剧院。这里每个人都有自愿离开剧院。
它被称为 Cyclicbarrier ,如果剧院在每个人进入剧院之前都不会开始。在这里,showman无法开始演出,直到所有人进入并抓住座位。一旦比赛结束,同样的障碍将适用于下一场秀

这里,人是线程,游戏是资源。

答案 1 :(得分:36)

关键区别在于CountDownLatch将线程分为服务员和到达者,而使用CyclicBarrier的所有线程都执行这两种角色。

  • 使用闩锁,服务员等待最后到达的线程到达,但那些到达的线程不会自行等待。
  • 有一个屏障,所有线程都会到达,然后等待最后一个到达。

你的闩锁示例暗示所有十个人都必须等待将石头抬起来。不是这种情况。一个更好的现实世界的例子是一个考试提示者,他耐心地等待每个学生交出他们的考试。学生完成考试后不会等待,可以自由离开。一旦最后一个学生参加考试(或时间限制到期),提示者就会停止等待并离开考试。

答案 2 :(得分:12)

真实世界的例子 我可以看到所有答案实际上都缺少一个真实的例子。就如何在软件领域中使用这些类一样

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

  2. <强>的CyclicBarrier 与上述情况相同。但假设文件是​​从P2P下载的。再次多个线程下载件。但是在这里,假设您希望在特定时间间隔之后对下载的片段进行实体检查。这里的循环屏障起着重要作用。在每个时间间隔之后,每个线程将在屏障处等待,以便与cyclibarrier相关联的线程可以进行完整性检查。由于CyclicBarrier

  3. ,可以多次完成此完整性检查

    如果不正确,请纠正我。

答案 3 :(得分:4)

用例1 假设您已将一个大型作业拆分为10个小任务,每个任务都是一个线程。在考虑完成工作之前,你必须等待10个任务从那个线程结束。

因此,主作业启动程序线程将CountDownLatch初始化为所使用的线程数,它将任务分配给线程,并等待锁存器使用await方法引发零。每个执行程序线程将在其任务结束时调用countDown。最后,当所有线程都完成时,将唤醒主线程,因此它认为所有工作都已完成。此方案使用CountDownLatch javadoc中描述的doneSignal锁存器。

用例2 假设您已将大型作业拆分为n * m个任务,分布在n个线程上。 m对应一个矩阵行,你有一个总计来计算每一行。在这种情况下,必须在每个任务结束后同步线程,以便计算行的总数。在这种情况下,使用线程数n初始化的CyclicBarrier用于等待每行计算的结束(实际上是m次)。

要比较两者,CountDownLatch应该只使用一次,CyclicBarrier可以使用多次,因为算法需要一组线程的同步点。

答案 4 :(得分:4)

CyclicBarrier是可以重复使用的,所以它更像是一场赛车巡回赛,每个人都会在一个航点上相遇,然后再继续巡回演出。

答案 5 :(得分:3)

理论差异:

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

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

现实生活中的例子: -

CountDownLatch:考虑一个IT世界场景,其中经理在开发团队(A和B)之间划分模块,并且他希望将其分配给QA团队,以便仅在两个团队完成任务时进行测试。 / p>

这里,经理线程作为主线程,开发团队作为工作线程。 Manager线程等待开发团队线程完成他们的任务。

CyclicBarrier:考虑相同的IT世界场景,其中经理在开发团队(A和B)之间划分模块。他休假并要求两队完成各自的任务,一旦完成任务,将其分配给QA团队进行测试。

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

答案 6 :(得分:1)

<强> CountDownLatch: 如果我们想要所有的线程都做

  

某事+倒计时

这样其他等待(计数到达零)线程可以继续,我们可以使用倒计时锁存。实际执行倒计时的所有先前线程都可以在这种情况下继续运行,但无法保证在latch.countdown()之后处理行处理后,等待其他线程到达latch.countdown() 但它保证其他等待线程只有在latch.await()达到零后才会进一步启动。

<强>的CyclicBarrier: 如果我们想要所有的线程

  

做点什么+等待共同点+做点什么

(每次等待调用都会减少线程继续进行的等待时间)

通过调用latch.countdown(),然后调用所有线程的latch.await(),CountDownLatch只能实现一次CyclicBarrier功能。

但你又无法重置/重复使用countdownlatch。

我使用CyclicBarrier的最好例子是初始化多个缓存(由多个线程加热),然后开始进一步处理,我想在Sync中重新初始化其他缓存。

答案 7 :(得分:0)

顾名思义,循环屏障可以循环使用。 例如: 我是一家公司,正在寻找各种工作门户网站提供的N份简历。 我有一个技能组数组,其中包含按优先顺序排序的技能。 对于ex java,c#,python。我想找到匹配java技能组的N个简历, 但如果我没有找到所需的没有。简历,我再次搜索 在下一个技能组等等。

我创建了一个工作人员,每个工作人员都在浏览简历 分配的工作提要。两名工人都将从初级工作开始 技能组搜索他们的工作提要。

执行搜索后,工作人员将检查N是否恢复 被找到。如果找到,工人将重置障碍物 返回。否则它将等待另一个工人完成。 如果仍未找到N个简历,则搜索将是 在技​​能组数组的下一个技能上再次恢复。 因此,可以在不需要的情况下递归/循环地调用搜索 创造一个新的循环障碍。

答案 8 :(得分:0)

这是我的观察结果:-----> 1.完成事件之后:创建时,在CountDownLatch中不能给出完成事件,但是在CyclicBarrier中可以给出完成事件。

  1. 计数:创建CyclicBarrier时,如果工作线程中还包括等待其他线程完成的主线程,则该线程不包括主线程。 创建CountDownLatch时,只需提及要等待完成的主线程有多少个工作线程。 CountDownLatch中包含主线程和工作线程的概念,创建锁存器时不包括主等待线程。

  2. await()的意图:在CyclicBarrier :: await()中,包括主线程在内的所有线程都是相等的,并且彼此等待。因此,应在所有线程(工作线程和主线程)中都提供await()。 CountDownLatch :: await()仅在主线程中给出,它使主线程等待,直到其他工作线程的计数变为0。因此,两个await()的内部实现都是不同的。

  3. 聚会线程和等待线程:CountDownLatch不能给出等待线程的数量,但是可以给聚会线程(cl.getCount()), CyclicBarrier不能提供任何等待线程cb.getNumberWaiting()和party(cb.getParties())

  4. 工作职责:countdownlatch中的工作线程需要执行countdown(),而await()由一个主线程完成。 在CycleBarrier工作器和主线程中,所有线程仅相互执行await()。

  5. 重用:可以重用CyclicBarrier。 cb.await()适用于新线程,例如t1,t2和main。对新线程t3,t4和main的cb.await()的第二次调用也有效。 Main将在两个调用中都等待,也就是说,系统在退出屏障后会自动在内部重置计数(或reset())。 CountDownLatch无法重复使用。 -cl.await()适用于新线程,例如t1,t2和main。主线程等待t1,t2完成。但是第二次cl.await()调用新线程t3,t4,而main不会等待。

这是支持它的代码...。

static class MyThread implements Runnable
{
    long waitTime;
    CyclicBarrier cyclicBarrier;
    CountDownLatch countdownlatch;
    MyThread(  long waitTime, CyclicBarrier cyclicBarrier, CountDownLatch countdownlatch){
        this.waitTime = waitTime;
        this.cyclicBarrier = cyclicBarrier;
        this.countdownlatch = countdownlatch;
    }
    @Override
    public void run() {

            try {

                Thread.sleep(waitTime);

                // Diff 4 -----> countdownlatch worker threads need to do countdown and await is done by one single main thread
                //, cyclicBarrier worker threads await on each other
                countdownlatch.countDown(); 
                cyclicBarrier.await();

                System.out.println("cyclicBarrier :: " + 
                        ", name :: " + Thread.currentThread().getName() 
                        + ", parties :: " + cyclicBarrier.getParties() 
                        + ", waiting :: "+ cyclicBarrier.getNumberWaiting()); 

                System.out.println("countdownlatch :: " + 
                            "name :: " + Thread.currentThread().getName()  +
                         ", parties :: "+countdownlatch.getCount() +
                         ", waiting :: " + "No method!!" ); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

    }

}

public static class MyCBFinishEvent implements Runnable{

    public void run() {

           System.out.println("All threads have reached common barrier point "
                        + ", CyclicBarrrierFinishEvent has been triggered");
           System.out.println("You can update shared variables if any");
    }

}

public static void main(String [] args) throws InterruptedException, BrokenBarrierException{
    //Diff 1 ----- > No finish event can be given in CountDownLatch
    //Diff 5 ------> CyclicBarrier no of worker threads includes main thread, 
    //CountDownLatch is just how many threads, the main waiting thread is not included in count.
    CyclicBarrier cb = new CyclicBarrier(3, new MyCBFinishEvent());
    CountDownLatch cl = new CountDownLatch(2);

    //Diff 2 ----> CountDownLatch cant give num of waiting threads, CyclicBarrier can getNumberWaiting threads
     System.out.println("Start CyclicBarrier - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
     System.out.println("Start CountDownLatch - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );

    Runnable t1 = new Thread(new MyThread( 10000, cb, cl));
    Runnable t2 = new Thread(new MyThread( 5000, cb, cl));
     Thread tt1 = new Thread(t1, "t1");
     Thread tt2 = new Thread(t2, "t2");
     tt1.start();
     tt2.start();

     //Diff 6 ---- > await meaning Main waits for t1 and t2 to complete, 
     //CyclicBarrier all are equal. each thread including main thread, if it wants to wait has to do await. 
     //CountDownLatch concept of waiting and workers. main thread await waits till other worker threads make count to 0.
     cb.await();
     cl.await();

     System.out.println("End CyclicBarrier call 1 - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
     System.out.println("End CountDownLatch call 1 - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );

     System.out.println("main start created t3, t4 - parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );

     Runnable t3 = new Thread(new MyThread( 6000, cb, cl));
        Runnable t4 = new Thread(new MyThread( 100, cb, cl));
         Thread tt3 = new Thread(t3, "t3");

         Thread tt4 = new Thread(t4, "t4");

         tt3.start();
         tt4.start();

        //Diff -3 -----> 
         //CyclicBarrier - can be reused, main thread waited for t3, t4 to complete.
         //CountDownLatch - for first cl.await(), main waited... second cl.await() call main did not wait!!! 
         cb.await();
         cl.await();


         System.out.println("End main - parties :: "+cb.getParties() + ", waiting :: " + cb.getNumberWaiting());
         System.out.println("end main parties :: "+cl.getCount() + ", waiting :: " + "No method!!" );

}

答案 9 :(得分:0)

对于CyclicBarrier ,我可以想到的一个实时示例;让我们想象一下有一群游客在节奏的旅行者上。一天中将有多个景点。速度驱动器知道有x个游客。到达第一个位置后,所有游客都会在不同时间返回并返回;但是节奏和旅行者必须等到所有游客返回。一旦它们全部返回,驾驶员将继续到下一个位置,并重复相同的过程。在这里,使用游客人数来初始化CyclicBarrier。每个游客就像一个线程,返回时,他们调用CyclicBarrier await(),以便等到所有其他游客回来。现在让我您所想

答案 10 :(得分:0)

CountDownLatch:“最佳现实生活示例-种族”

(任何种族类型,例如赛马,自行车赛或赛车等)

赛马也是可以使用CountDownLatch实现的最佳现实生活示例之一。

  1. 比赛距离将为“宣布”或“固定”。 (竞赛距离,以公里/英里/码为单位)
  2. 许多马匹将参加比赛。 (马数)
  3. 开始比赛之前,所有马匹都会到达起点或起点。
  4. 一旦所有马匹到达起跑门/起跑点,则只有比赛开始。
  5. 一旦发出信号-所有马匹将立即开始/全部释放。
  6. 所有马匹都将开始奔跑...完成比赛距离/到达终点。
  7. 所有马匹都完成“比赛距离”或“到达终点”后。
  8. 我们获得所有马匹的位置/位置列表-获胜者列表。
  9. 宣布比赛获胜者

我们使用两个CountDownLatch作为开始并结束比赛。

CountDownLatch start  = new CountDownLatch(1); // Start signal - will be always 1 only once All Horses Reach at Starting Point signal for START RACE.

CountDownLatch finish = new CountDownLatch(horses_Count); // Number of horses participating in Race nothing but Threads/Horses Count.

马-只是线程。

start.await(): 每个马/线程一旦到达起始门/起始点,它将等待其他马/线程到达起始门/起始点--start.await();

start.countDown():一旦所有马匹/线程都到达起始门/起始点,我们发出信号就表示要开始比赛-start.countDown(); 调用start.countDown();时,它将notifyAll()引导所有等待中的马匹/线程开始比赛。

finish.await()::一旦所有马匹/线程开始比赛,所有马匹/线程将等待主线程完成或完成-finish.await();

finish.countDown()::每匹马/线程完成比赛后,将减少计数。上一匹马/线程将计数减少到零,然后种族完成/完成finish.countDown(); 当计数达到零时调用finish.countDown();时,它将notify()等待主线程-所有马匹/线程完成/完成RACE。