CountDownLatch与信号量
有没有使用的好处
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情况下锁存器不能被重用,更重要的是,你需要事先知道将创build多less个线程(或者等到它们全部在创build锁存器之前启动)。
那么在什么情况下,闩锁可能更可取?
CountDown锁存器经常用于和你的例子完全相反的地方。 通常,在“await()”上会有许multithreading阻塞,当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(); }
这就是说,CountDown闩锁可以安全地用在你的例子中显示的方式。
CountDownLatch用于启动一系列线程,然后等待所有线程完成(或直到他们调用countDown()
给定的次数。
信号量用于控制使用资源的并发线程的数量。 该资源可以像文件一样,也可以通过限制执行的线程数量成为cpu。 信号量的计数可以随着不同的线程调用acquire()
和release()
而升降。
在你的例子中,你基本上使用Semaphore作为一种Count Up Latch。 鉴于你的意图是等待所有的线程完成,使用CountdownLatch
使你的意图更清晰。
简短的摘要:
-
信号量和CountDownLatch服务于不同的目的。
-
使用信号量来控制线程对资源的访问。
-
使用CountDownLatch等待所有线程的完成
来自javadocs的信号量定义:
信号量拥有一套许可证。 如果需要,每个获取()都会阻塞,直到获得许可证为止。 每个版本()都添加一个许可证,可能会释放一个阻止的获取者。
但是,没有使用实际的许可证对象; 信号只是保持可用数量的计数,并采取相应的行动。
它是如何工作的 ?
信号量用于控制正在使用资源的并发线程数。该资源可以是共享数据或代码块( 临界区 )或任何文件。
信号量的计数可以随着不同的线程调用acquire
()和release
()而升降。 但是在任何时候,线程的数量都不能超过信号量。
信号量用例:
- 限制对磁盘的并发访问(由于竞争性的磁盘search,这可能会导致性能下降)
- 线程创build限制
- JDBC连接池/限制
- networking连接限制
- 限制CPU或内存密集型任务
看看这篇文章中的信号量使用。
来自javadocs的CountDownLatch定义:
同步协助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
它是如何工作的?
CountDownLatch通过使用一个线程数来初始化一个计数器来工作,每当一个线程完成其执行时递减。 当计数达到零时,表示所有的线程都已经完成执行,线程等待锁存器恢复执行。
CountDownLatch用例:
- 实现最大的并行性:有时我们想要同时启动多个线程来实现最大的并行性
- 在开始执行之前等待N个线程完成
- 死锁检测。
看看这篇文章 ,明白了解CountDownLatch的概念。
在这篇文章中也可以看看Fork Join Pool 。 它与CountDownLatch有一些相似之处。
说你走进高尔夫专卖店,希望find一个四人组,
当你排队proshopVendorSemaphore.acquire()
一位专业的店员时,基本上你会打电话给proshopVendorSemaphore.acquire()
,一旦你开了一个开球时间,你就叫proshopVendorSemaphore.release()
。注意:任何免费的服务员都可以为您服务,即共享资源。
现在你走起步,他启动一个CountDownLatch(4)
并调用await()
等待其他人,你的部分,你所谓的签入,即CountDownLatch
。 countDown()
和其余的四人组。 当所有到达时,启动器继续( await()
调用返回)
现在,在九洞之后,你们每个人都rest一下,假设让他再次涉及起跑者,他使用一个新的CountDownLatch(4)
开球10洞,和洞1一样的等待/同步。
但是,如果启动器使用CyclicBarrier
开始,他可以重置10孔中的相同实例,而不是使用&throw的第二个锁存器。
从可用的来源来看,这两个课程的实施没有什么神奇的,所以它们的performance应该差不多。 select让你的意图更明显的一个。
CountdownLatch使线程在await()方法上等待,直到count达到零的时间。 所以也许你想让所有的线程等待3个调用的东西,然后所有的线程都可以继续。 锁存器通常不能被重置。
信号量允许线程检索许可证,这可以防止太多的线程立即执行,如果它不能获得它需要的许可证,则阻塞线程。 许可证可以返回到信号量,允许其他等待线程继续。