106

使用有什么好处吗

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 的情况下,闩锁不能被重用,更重要的是,您需要提前知道将创建多少线程(或者等到它们都启动后再创建闩锁。)

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

4

7 回答 7

118

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

于 2008-10-08T19:53:50.693 回答
72

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

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

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

于 2008-10-08T20:42:58.440 回答
31

简短的摘要:

  1. SemaphoreCountDownLatch服务于不同的目的。

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

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

Semaphore来自 Javadocs 的定义:

ASemaphore维护一组许可证。acquire()如有必要,每个人都会阻止,直到获得许可,然后再接受。每个都release()添加一个许可证,可能会释放一个阻塞的收购方。

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

它是如何工作的?

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

a 的计数可以随着不同的线程调用和Semaphore而上下波动。但是在任何时候,您都不能拥有比信号量更多的线程数。acquire()release()

Semaphore用例:

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

查看这篇文章以了解信号量的使用。

CountDownLatch来自 Javadocs 的定义:

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

它是如何工作的?

CountDownLatch通过使用线程数初始化计数器来工作,每次线程完成其执行时该计数器都会递减。当计数为零时,意味着所有线程都已完成执行,等待锁存器的线程恢复执行。

CountDownLatch用例:

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

看看这篇文章可以清楚地理解CountDownLatch概念。

也可以在本文中查看Fork Join Pool。它与.CountDownLatch

于 2015-11-10T07:12:55.063 回答
6

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

当您排队从专业店服务员那里获得开球时间时,基本上是您打电话proshopVendorSemaphore.acquire(),一旦您获得开球时间,您就打电话给proshopVendorSemaphore.release()。注意:任何免费服务员都可以为您服务,即共享资源。

现在你走到起动器,他开始一个CountDownLatch(4)并打电话await()等待其他人,对于你来说,你称之为签到即CountDownLatchcountDown()其余的四人组也是如此。当所有人都到达时,启动器继续前进(await()调用返回)

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

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

于 2013-08-01T22:16:36.537 回答
2

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

于 2008-10-08T19:31:35.450 回答
0

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

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

于 2008-10-08T19:25:10.157 回答
0

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

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

于 2021-08-16T11:21:36.160 回答