繁体   English   中英

CountDownLatch 与信号量

[英]CountDownLatch vs. Semaphore

使用有什么好处吗

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

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

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

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

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

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

简短的摘要:

  1. SemaphoreCountDownLatch用于不同的目的。

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

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

Javadocs 中的Semaphore定义:

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

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

它是如何工作的?

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

当不同的线程调用acquire()release()时, Semaphore的计数可以上下波动。 但是在任何时候,线程数都不能超过信号量计数。

Semaphore用例:

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

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

来自 Javadocs 的CountDownLatch定义:

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

它是如何工作的?

CountDownLatch工作原理是使用线程数初始化计数器,每次线程完成执行时该计数器都会递减。 当计数为零时,表示所有线程都已完成执行,等待闩锁的线程继续执行。

CountDownLatch用例:

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

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

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

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

当您排队从一位专业商店服务员那里获得开球时间时,本质上您调用了proshopVendorSemaphore.acquire() ,一旦您获得开球时间,您就会调用proshopVendorSemaphore.release() 。注意:任何免费服务员都可以为您服务,即共享资源。

现在你走到 starter,他启动一个CountDownLatch(4)并调用await()等待其他人,对于你来说,你调用了 check-in ,即CountDownLatch countDown()等四人组的其余部分也是如此。 全部到达后,starter 继续执行( await()调用返回)

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM