[英]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
会使您的意图更加清晰。
简短的摘要:
Semaphore
和CountDownLatch
用于不同的目的。
使用Semaphore
来控制线程对资源的访问。
使用CountDownLatch
等待所有线程完成
Javadocs 中的Semaphore
定义:
Semaphore
维护一组许可。 如有必要,每个acquire()
阻塞,直到获得许可为止,然后获取它。 每个release()
添加一个许可,可能会释放一个阻塞的获取者。
但是,没有使用实际的许可对象; Semaphore
只是计算可用的数量并相应地采取行动。
它是如何工作的?
信号量用于控制使用资源的并发线程数。资源可以是共享数据、代码块(临界区)或任何文件。
当不同的线程调用acquire()
和release()
时, Semaphore
的计数可以上下波动。 但是在任何时候,线程数都不能超过信号量计数。
Semaphore
用例:
看看这篇文章,了解信号量的使用。
来自 Javadocs 的CountDownLatch
定义:
一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
它是如何工作的?
CountDownLatch
工作原理是使用线程数初始化计数器,每次线程完成执行时该计数器都会递减。 当计数为零时,表示所有线程都已完成执行,等待闩锁的线程继续执行。
CountDownLatch
用例:
看看这篇文章可以清楚地理解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.