CountDownLatch与信号量


92

使用有什么好处吗

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

除了在#2的情况下不能重用闩锁之外,更重要的是,您需要提前知道将创建多少个线程(或者等到它们全部启动后再创建闩锁)。

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

Answers:


109

CountDown锁存器经常与您的示例完全相反。通常,您会在“ 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();
}

综上所述,可以按照示例中所示的方式安全地使用CountDown锁存器。


1
谢谢。因此,如果有多个线程可以在闩锁上等待,那么我的两个示例将不相等...除非sem.acquire(num_threads); 后面跟sem.release(num_threads);?我认为这将使它们再次等效。
finnw

从某种意义上讲,是的,只要每个线程都叫acquire,然后释放。严格来说,不是。使用闩锁,所有线程都有资格同时启动。有了信号灯,它们就可以彼此合格(这可能导致不同的线程调度)。
詹姆斯·谢克

Java文档似乎暗示此CountdownLatch非常适合他的示例:docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/…。具体地说,“初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个动作或某个动作已经完成N次。”
克里斯·莫里斯

你是对的。我将稍作更新,以反映这是我所看到的CountDownLatch的最常见用途,而这是预期用途。
James Schek

11
这回答了以下问题:CountDownLatch最常使用的是什么?它没有回答有关使用CountDownLatch相对于信号量的优点/区别的原始问题。
Marco Lackovic

67

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

信号量用于控制使用资源的并发线程数。该资源可以是类似于文件的资源,也可以是通过限制执行线程数的CPU。当不同的线程调用acquire()和时,信号量的数量可以上升和下降release()

在您的示例中,您实际上是在使用信号量作为Count UP Latch。鉴于您的意图是等待所有线程完成,因此使用CountdownLatch可以使您的意图更加清晰。


22

简短的摘要:

  1. 信号量CountDownLatch具有不同的用途。

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

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

来自javadocs的信号量定义:

一个信号灯维护一组允许的。如有必要,每个Acquisition()会阻塞,直到获得许可为止,然后再获取许可。每个release()添加一个许可,有可能释放阻塞获取者。

但是,没有使用实际的许可对象。该信号量只是不断数量的计数可用,采取相应的行动。

它是如何工作的 ?

信号量用于控制正在使用资源的并发线程的数量。该资源可以是共享数据,代码块(关键部分)或任何文件。

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

信号量用例:

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

看看这篇文章中的信号量用法。

来自Javadocs的CountDownLatch定义:

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

它是如何工作的?

CountDownLatch的工作原理是使用线程数初始化计数器,每次线程完成执行时,计数器都会递减。当count达到零时,表示所有线程已完成其执行,并且等待闩锁的线程将恢复执行。

CountDownLatch用例:

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

请看一下这篇文章,以清楚地了解CountDownLatch概念。

也可以在本文中查看Fork Join Pool。它与CountDownLatch有一些相似之处。


7

假设您走进一家高尔夫专业商店,希望找到一个四人行,

当您排队从一位专业商店服务员proshopVendorSemaphore.acquire()那里获取开球时间时,您实际上打了个电话,一旦获得开球时间,便打了电话proshopVendorSemaphore.release()。注意:任何免费服务员都可以为您服务,即共享资源。

现在,您走到入门者那里,他开始了a CountDownLatch(4)并打电话await()等待其他人,您叫您已签到的部分,即CountDownLatchcountDown()其他四人组也是如此。当所有到达时,启动器继续进行(await()调用返回)

现在,假设每个人都休息了9个洞之后,假设又让初学者参与进来,他使用“ new” CountDownLatch(4)开球10号洞,与1号洞的等待/同步相同。

但是,如果入门者使用a CyclicBarrier开头,那么他可能已经在Hole 10中重置了相同的实例,而不是第二个使用&throw的闩锁。


1
我不确定我是否理解您的答案,但是如果您试图描述CountdownLatch和Semaphore的工作原理,那么这不是问题的主题。
finnw

10
不幸的是,我对高尔夫一无所知。
环形承载者

但是首发内容也可以使用.acquire(players)完成,并随着发行增加发行量。countdownlatch似乎功能较少,没有可重用性。
Lassi Kinnunen

1

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


0

CountdownLatch使线程等待该await()方法,直到计数达到零为止。因此,也许您希望所有线程都等到3次调用某个东西,然后所有线程才能运行。一个Latch一般不能复位。

A Semaphoreallow线程检索许可,这阻止了太多线程一次执行,如果无法获得它需要继续进行的许可,则会阻塞。可以将许可返回给a,以Semaphore允许其他等待线程继续进行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.