【问题标题】:CountDownLatch vs. SemaphoreCountDownLatch 与信号量
【发布时间】:2010-09-16 02:19:18
【问题描述】:

使用有什么好处

java.util.concurrent.CountdownLatch

而不是

java.util.concurrent.Semaphore?

据我所知,以下片段几乎是等价的:

1.信号量

final Semaphore sem = new Semaphore(0);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        sem.release();
      }
    }
  };
  t.start();
}

sem.acquire(num_threads);

2:倒计时

final CountDownLatch latch = new CountDownLatch(num_threads);
for (int i = 0; i < num_threads; ++ i)
{
  Thread t = new Thread() {
    public void run()
    {
      try
      {
        doStuff();
      }
      finally
      {
        latch.countDown();
      }
    }
  };
  t.start();
}

latch.await();

除了在 #2 的情况下,latch 不能被重用,更重要的是你需要提前知道将创建多少线程(或者等到它们都启动后再创建 latch。)

那么在什么情况下闩锁更可取?

【问题讨论】:

    标签: java multithreading concurrency semaphore countdownlatch


    【解决方案1】:

    CountDownLatch 经常用于与您的示例完全相反的情况。通常,await() 上会有很多线程阻塞,当 countown 达到零时,它们都会同时启动。

    final CountDownLatch countdown = new CountDownLatch(1);
    
    for (int i = 0; i < 10; ++ i) {
       Thread racecar = new Thread() {    
          public void run() {
             countdown.await(); //all threads waiting
             System.out.println("Vroom!");
          }
       };
       racecar.start();
    }
    System.out.println("Go");
    countdown.countDown();   //all threads start now!
    

    您也可以将其用作 MPI 样式的“屏障”,使所有线程在继续之前等待其他线程赶上某个点。

    final CountDownLatch countdown = new CountDownLatch(num_thread);
    
    for (int i = 0; i < num_thread; ++ i) {
       Thread t= new Thread() {    
          public void run() {
             doSomething();
             countdown.countDown();
             System.out.printf("Waiting on %d other threads.",countdown.getCount());
             countdown.await();     //waits until everyone reaches this point
             finish();
          }
       };
       t.start();
    }
    

    总而言之,CountDownLatch 可以按照您在示例中显示的方式安全地使用。

    【讨论】:

    • 谢谢。因此,如果多个线程可以在闩锁上等待,我的两个示例将不等价……除非 sem.acquire(num_threads);后面是 sem.release(num_threads);?我认为这会使它们再次等效。
    • 从某种意义上说,是的,只要每个线程调用acquire后跟release。严格来说,没有。使用闩锁,所有线程都有资格同时启动。使用信号量,它们一个接一个地成为合格的(这可能导致不同的线程调度)。
    • Java 文档似乎暗示这个 CountdownLatch 非常适合他的示例:docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…。具体来说,“一个初始化为 N 的 CountDownLatch 可以用来让一个线程等待,直到 N 个线程完成某个动作,或者某个动作已经完成 N 次。”
    • 你是对的。我会稍微更新我的答案,以反映这是我见过的最常见的 CountDownLatch 用途,而不是预期用途。
    • 这回答了问题CountDownLatch 最常使用的是什么?它没有回答关于使用CountDownLatch 优于信号量的优点/差异的原始问题。
    【解决方案2】:

    CountDownLatch 用于启动一系列线程,然后等待它们全部完成(或直到它们调用countDown() 给定次数。

    信号量用于控制正在使用资源的并发线程数。该资源可以是文件之类的东西,也可以是通过限制执行的线程数量的 cpu。随着不同线程调用acquire()release(),信号量的计数可以上下浮动。

    在您的示例中,您实际上是在使用信号量作为一种计数UPLatch。鉴于您的意图是等待所有线程完成,使用 CountdownLatch 会使您的意图更加清晰。

    【讨论】:

      【解决方案3】:

      简短的总结:

      1. SemaphoreCountDownLatch 有不同的用途。

      2. 使用Semaphore 控制线程对资源的访问。

      3. 使用CountDownLatch 等待所有线程完成

      Semaphore 来自 Javadocs 的定义:

      Semaphore 维护一组许可。如有必要,每个acquire() 都会阻塞,直到 permit 可用,然后接受它。每个release() 添加一个许可,可能会释放一个阻塞的收购方。

      但是,没有使用实际的许可对象; Semaphore 只是对可用数量进行计数并采取相应措施。

      它是如何工作的?

      信号量用于控制使用资源的并发线程数。该资源可以是共享数据、代码块(临界区)或任何文件。

      Semaphore 的计数可以随着不同线程调用acquire()release() 而上升和下降。但是在任何时候,线程数都不能超过信号量。

      Semaphore 用例:

      1. 限制对磁盘的并发访问(由于性能下降 竞争磁盘寻道)
      2. 线程创建限制
      3. JDBC 连接池/限制
      4. 网络连接限制
      5. 限制 CPU 或内存密集型任务

      看看这个article 用于信号量。

      CountDownLatch 来自 Javadocs 的定义:

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

      它是如何工作的?

      CountDownLatch 通过使用线程数初始化计数器来工作,每次线程完成其执行时该计数器都会递减。当count为0时,表示所有线程都执行完毕,等待latch的线程继续执行。

      CountDownLatch 用例:

      1. 实现最大并行度:有时我们希望启动多个 线程同时实现最大并行度
      2. 等待 N 个线程完成后再开始执行
      3. 死锁检测。

      看看这个article 以清楚地理解CountDownLatch 的概念。

      看看Fork Join Pool 这个article。它与CountDownLatch 有一些相似之处。

      【讨论】:

        【解决方案4】:

        假设你走进高尔夫专卖店,希望找到一个四人组,

        当您排队等候一位专卖店服务员的开球时间时,基本上是您致电proshopVendorSemaphore.acquire(),一旦您获得开球时间,您就致电proshopVendorSemaphore.release()。注意:任何免费服务员都可以服务你,即共享资源。

        现在您走到首发位置,他启动 CountDownLatch(4) 并呼叫 await() 以等待其他人,对于您而言,您调用了签到,即 CountDownLatch.countDown(),其余四人组也是如此。当所有人都到达时,starter 会继续(await() 调用返回)

        现在,当你们每个人都休息了九个洞之后,假设让先发球员再次参与进来,他使用“新”CountDownLatch(4) 在第 10 洞开球,与第 1 洞相同的等待/同步。

        但是,如果启动器使用 CyclicBarrier 开始,他可以在第 10 洞重置相同的实例,而不是使用 & throw 的第二个闩锁。

        【讨论】:

        • 我不确定我是否理解您的回答,但如果您试图描述 CountdownLatch 和 Semaphore 的工作原理,那不是问题的主题。
        • 很遗憾我对高尔夫一无所知。
        • 但是入门的东西也可以用 .acquire(players) 来完成,并随着发布增加发布的数量。倒计时锁似乎只是功能较少且没有可重用性。
        【解决方案5】:

        查看免费提供的源代码,这两个类的实现没有什么神奇之处,因此它们的性能应该大致相同。选择让您的意图更加明显的那个。

        【讨论】:

          【解决方案6】:

          CountdownLatch 使线程等待await() 方法,直到计数达到零。因此,也许您希望所有线程等到 3 次调用某事,然后所有线程都可以运行。 Latch一般是不能重置的。

          Semaphore 允许线程检索许可,从而防止太多线程同时执行,如果无法获得继续执行所需的许可,则阻塞。可以将许可返回到Semaphore,允许其他等待线程继续进行。

          【讨论】:

            【解决方案7】:

            信号量通过使用计数器来控制对共享资源的访问。如果计数器大于零,则允许访问。如果为零,则拒绝访问。计数器正在计算允许访问共享资源的许可。因此,要访问资源,线程必须获得信号量的许可。

            CountDownlatch 使线程等待一个或多个事件发生。 countDownLatch 最初是使用在释放锁存器之前发生的事件数的计数创建的。每次发生事件时,计数都会递减。

            【讨论】:

              猜你喜欢
              • 2017-08-24
              • 2010-09-09
              • 1970-01-01
              • 2012-09-01
              • 2013-05-30
              • 1970-01-01
              • 2023-03-25
              • 2011-03-31
              相关资源
              最近更新 更多