【问题标题】:Java concurrency: Countdown latch vs Cyclic barrierJava 并发:倒计时锁存器与循环障碍
【发布时间】:2011-05-09 07:12:25
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 锁存器用于等待事件;屏障用于等待其他线程。 - Java 并发实践,B.Goetz 等人。

标签: java concurrency countdownlatch cyclicbarrier


【解决方案1】:

@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.

【讨论】:

    【解决方案2】:

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

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

        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 -- 没有任何性能效率损失)

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

          • 它不必是主线程。它可以是创建 CountDownLatch 并与其他非主线程共享的任何线程。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

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

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

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

            【讨论】:

            • "在 CyclicBarrier 中,只有一种类型的线程" ...在其他线程调用 .await() 之前,它们的“等待角色”是相等的,但它们可能“在他们做什么”。而且它们都必须是相同类型或不同类型的绝对不同的线程实例(!),而在 CountDownLatch 中,同一线程可能会调用 countDown() 并影响结果。
            • 我同意 CountDownLatch 本质上需要两个角色:一个用于 countDown 的客户端和一个用于等待的客户端。另一方面,CyclicBarrier 客户端可以使用 await 方法正常运行。
            【解决方案8】:

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

            为了说明循环障碍的行为,我做了一些示例代码。一旦障碍物倾倒,它就会自动重置,以便可以再次使用(因此它是“循环的”)。运行程序时,观察“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();
                    }
                }
            }
            

            【讨论】:

              【解决方案9】:

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

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

              【讨论】:

                【解决方案10】:

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

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

                另一个典型的用法是将一个问题分成 N 个部分, 用执行该部分的 Runnable 描述每个部分,并 对锁存器进行倒计时,并将所有 Runnables 排队到 Executor。 当所有子部分都完成后,协调线程将能够 通过等待。 (当线程必须在 这种方式,改为使用 CyclicBarrier。)

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

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

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

                【讨论】:

                  【解决方案11】:

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

                  【讨论】:

                    【解决方案12】:

                    还有一个区别。

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

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

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

                    我只希望CountDownLatch可以被重置!

                    【讨论】:

                    • 我认为这是更好地显示理论差异的答案。可以通过多次调用一个方法来打破闩锁这一事实,而屏障需要精确数量的线程来等待()。
                    • 对 - 这是主要区别:CountDownLatch-->NumberOfCalls, CyclicBarrier-->NumberOfThreads
                    • 我同意 CountDownLatch 可重置会很棒 - 我用来实现粗略等待通知的解决方法是在输入受保护的代码块时立即新建一个 CountDownLatch (当锁存器达到零时)。当然,这并不适用于所有情况/范围,但我认为值得注意的是,它是金发姑娘情况下的一种选择。
                    • 关于此主题的最佳答案之一。 Java Concurrency in Practice - 说同样的话:Latches are for waiting for events; barriers are for waiting for other threads.。了解这两者之间的区别的主要和基本点。
                    • Java 8 文档说“初始化为 N 的 CountDownLatch 可用于使一个线程等待 N 个线程完成某个操作,或者某个操作已完成 N 次。”在我看来:CountDownLatch--> NumberOfCalls 或 CountDownLatch --> NumberOfThreads
                    【解决方案13】:

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

                    CountDownLatch 被初始化为 给定计数。 await 方法块 直到当前计数达到零 由于调用了 countDown() 方法,之后所有等待 线程被释放并且任何 后续调用 await return 立即地。这是一击 现象——计数不能 重置。如果您需要一个版本 重置计数,考虑使用 循环屏障。

                    来源1.6 Javadoc

                    【讨论】:

                    • 如果他们的区别只是可以被重置,那么CyclicBarrier可能会更好地命名为ResetableCountDownLatch,因为它们的区别更有意义。
                    【解决方案14】:

                    一个主要区别是CyclicBarrier 接受一个(可选的)可运行任务,该任务在满足常见障碍条件时运行。

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

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

                    【讨论】:

                    • "它还允许您获取在屏障处等待的客户端数量以及触发屏障所需的数量。一旦触发,屏障将被重置并可以再次使用。"我真的很喜欢这一点。我读过的几篇文章建议 CyclicBarrier 是循环的,因为您调用了 reset() 方法。确实如此,但他们不常提及的是,屏障一被触发就会自动重置。我将发布一些示例代码来说明这一点。
                    • @Kevin Lee 感谢“屏障一旦被触发就会自动重置。”所以不需要在代码中调用reset()。
                    猜你喜欢
                    • 2018-01-16
                    • 2014-09-07
                    • 2020-05-18
                    • 2011-11-20
                    • 2017-01-18
                    • 1970-01-01
                    • 2021-10-14
                    • 1970-01-01
                    相关资源
                    最近更新 更多