CountDownLatch与Semaphore的区别

113

使用java.util.concurrent.CountdownLatch相对于java.util.concurrent.Semaphore是否有优势?就我所知,以下片段几乎是等效的:

1. Semaphore

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: CountDownLatch

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

除了第二种情况下的闩锁无法重复使用,更重要的是您需要事先知道将创建多少个线程(或在所有线程启动之前等待,然后再创建闩锁)。那么,在什么情况下闩锁可能更可取呢?
7个回答

124

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

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可以安全地按照您在示例中展示的方式使用。


1
谢谢。所以如果多个线程可以等待闩锁,那么我的两个例子就不等价了...除非sem.acquire(num_threads);后面跟着sem.release(num_threads);?我认为这样它们又变得等价了。 - finnw
从某种意义上说,是的,只要每个线程都调用acquire然后是release。严格来说,不是这样的。使用latch时,所有线程都有资格同时启动。而使用信号量,则一个接一个地有资格启动(这可能会导致不同的线程调度)。 - James Schek
14
这回答了“CountDownLatch的最常用途是什么?”这个问题,但它并没有回答原问题,即使用CountDownLatch相比Semaphore的优势/区别。 - Marco Lackovic
如果使用信号量与不使用信号量完成相同的任务相比,前者提供了任何优势,那么答案会更好。 - Lassi Kinnunen
CountDownLatch不仅可以按照OP的方式“安全使用”,而且实际上它是CountDownLatch的预期使用场景之一,其中Semaphore并不适合,至少在语义上是这样。从概念上讲,Semaphore用于并发访问共享资源,而CountDownLatch用于等待某些任务完成后才能抬起闸门让其他线程出去/运行。 - wlnirvana
显示剩余2条评论

76

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

Semaphore 用于控制使用资源的并发线程数量。该资源可以是文件等任何东西,也可以通过限制执行线程的数量来控制 CPU 使用情况。Semaphore 上的计数可随不同的线程调用 acquire()release() 方法而增加或减少。

在您的示例中,您基本上将 Semaphore 用作一种 CountUPLatch。鉴于您的意图是等待所有线程完成,使用 CountdownLatch 可以使您的意图更加明确。


38

简短摘要:

  1. SemaphoreCountDownLatch有不同的用途。

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

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

Semaphore定义从docs.oracle.com Semaphore

一个Semaphore(信号量)维护一组许可,每个 acquire() 方法会阻塞直到有许可证可用,然后获取它。每个 release() 方法都会添加一个许可证,并且可能释放阻塞的获取器。但是,没有实际的许可对象被使用;Semaphore 只是保持可用数量的计数,并相应地执行操作。 信号量用于控制并发访问诸如文件、网络套接字等资源的线程数。 Semaphore 初始化为计数器值,提供最大许可证数。 acquire() 将减少许可证数量。release() 将增加许可证数量。在任何时候,不可能拥有超过允许许可证计数的拥有者数。 当您拥有有限数量的资源时非常有用-这可以是线程数量、网络套接字、网络打印机等。 Javadocs 中 CountDownLatch 的定义: 一个同步辅助工具,允许一个或多个线程等待一组在其他线程中执行的操作完成。

它是如何工作的?

CountDownLatch 通过设置一个计数器来初始化线程数量。当一个线程完成其执行时,计数器会递减。当所有线程都完成执行时,计数器将变为零。

CountDownLatch 的用例:by howtodoinjava.com by Lokesh Gupta

  1. 最大并行性
  2. 在开始执行其他任务之前等待所有线程完成
  3. 死锁检测

CountDownLatch和Object类中的wait/notify方法有什么不同? - Hiro_Hamada

6
当你走进高尔夫球道专卖店,希望找到一个四人组时,
当你排队向专卖店的任何一位服务员预定出发时间时,实际上相当于调用了 proshopVendorSemaphore.acquire() 方法,当你得到了出发时间,就相当于调用了 proshopVendorSemaphore.release() 方法。注意:任何一位空闲服务员都可以为您提供服务,即共享资源。
现在你走到起始点,工作人员启动了一个 CountDownLatch(4) 并调用了 await() 方法等待其他人到达。你已经完成了登记,即调用了 CountDownLatch.countDown() 方法,其他三人也这样做了。当所有人都到齐后,起始员工会发出前进信号(await() 调用返回)。
现在,在前九个洞的每次休息后,让我们假设再次涉及到起始员工,他使用 'new' CountDownLatch(4) 准备开启第十洞,与第一个洞的等待/同步方式相同。
然而,如果起始员工一开始就使用了一个 CyclicBarrier,那么在第十洞时,他可以重置同一个实例,而不是使用和丢弃第二个门闩。

1
我不确定我理解你的回答,但如果你试图描述CountdownLatch和Semaphore的工作原理,那不是问题的主题。 - finnw
13
很遗憾,我对高尔夫一无所知。 - ring bearer
但是启动器的东西也可以使用.acquire(players)来完成,并通过release增加释放计数。CountDownLatch似乎只具有较少的功能和不可重用性。 - Lassi Kinnunen

2

看着这两个类的免费源代码,它们的实现并没有什么神奇之处,因此它们的性能应该差不多。选择那个让你的意图更明显的。


1

CountdownLatch 使线程等待 await() 方法,直到计数达到零为止。因此,您可能希望所有线程等待3次调用某些内容,然后所有线程都可以继续执行。通常情况下,Latch 无法重置。

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


简单易懂的解释 - undefined

0

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

CountDownLatch使线程等待一个或多个事件发生。CountDownLatch最初创建时具有在释放闩锁之前必须发生的事件数量的计数。每次发生事件时,计数会递减。


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