有人可以帮我理解Java CountDownLatch
是什么以及何时使用它?
我对这个程序的工作原理并不十分清楚 . 据我所知,所有三个线程立即启动,每个线程将在3000ms后调用CountDownLatch . 倒数会逐一减少 . 在锁存器变为零之后,程序打印“已完成” . 也许我理解的方式不正确 .
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Processor implements Runnable {
private CountDownLatch latch;
public Processor(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
System.out.println("Started.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
}
// ------------------------------------------------ -----
public class App {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0
ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool
for(int i=0; i < 3; i++) {
executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
}
try {
latch.await(); // wait until latch counted down to 0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed.");
}
}
11 回答
是的,你理解正确 .
CountDownLatch
在锁存原理中工作,主线程将等到门打开 . 一个线程等待创建CountDownLatch
时指定的n个线程 .任何线程,通常是应用程序的主线程,调用
CountDownLatch.await()
将等到计数达到零或被另一个线程中断 . 一旦完成或准备好,所有其他线程都需要通过调用CountDownLatch.countDown()
来倒计时 .一旦计数达到零,等待线程就会继续 .
CountDownLatch
的一个缺点/优点是它不可重复使用:一旦计数达到零,你就不能再使用CountDownLatch
了 .Edit:
当一个线程(如主线程)需要等待一个或多个线程完成时才使用
CountDownLatch
,然后才能继续处理 .在Java中使用
CountDownLatch
的典型示例是服务器端核心Java应用程序,它使用服务体系结构,其中多个服务由多个线程提供,并且应用程序无法在所有服务成功启动之前开始处理 .附: OP的问题有一个非常简单的例子,所以我没有包含一个 .
Java中的
CountDownLatch
是一种同步器,它允许一个Thread
在开始处理之前等待一个或多个Thread
.CountDownLatch
在锁存原理上工作,线程将一直等到门打开 . 一个线程在创建CountDownLatch
时等待n
指定的线程数 .例如
final CountDownLatch latch = new CountDownLatch(3);
这里我们将计数器设置为3 .
任何线程,通常是应用程序的主线程,调用
CountDownLatch.await()
将等到计数达到零或被另一个Thread
中断 . 一旦完成或准备好工作,所有其他线程都需要通过调用CountDownLatch.countDown()
来倒计时 . 一旦计数达到零,Thread
等待开始运行 .这里的计数通过
CountDownLatch.countDown()
方法递减 .调用
await()
方法的Thread
将等到初始计数达到零 .要使计数为零,其他线程需要调用
countDown()
方法 . 一旦计数变为零,调用await()
方法的线程将恢复(开始执行) .CountDownLatch
的缺点是它不可重复使用:一旦计数变为零,它就不再可用了 .尼古拉B解释得非常好,但是例子有助于理解,所以这里有一个简单的例子......
当我们想要等待多个线程完成其任务时使用它 . 它类似于加入线程 .
Where we can use CountDownLatch
考虑一个我们有需求的场景,我们有三个线程“A”,“B”和“C”,我们只想在“A”和“B”线程完成或部分完成其任务时启动线程“C” .
It can be applied to real world IT scenario
考虑一种情况,管理者在开发团队(A和B)之间划分模块,并且他希望将其分配给QA团队,以便仅在两个团队完成任务时进行测试 .
Output of above code will be:
分配给开发团队devB的任务
分配给开发团队devA的任务
任务由开发团队devB完成
任务由开发团队devA完成
任务分配给QA团队
任务由QA团队完成
这里 await() 方法等待countdownlatch标志变为0, countDown() 方法将countdownlatch标志减1 .
Limitation of JOIN: 以上示例也可以通过JOIN实现,但JOIN不能在两种情况下使用:
当我们使用ExecutorService而不是Thread类来创建线程时 .
修改以上示例,其中Manager在开发完成80%任务后立即将代码切换到QA团队 . 这意味着CountDownLatch允许我们修改可用于等待另一个线程进行部分执行的实现 .
什么时候使用这样的东西的一个很好的例子是Java Simple Serial Connector,访问串行端口 . 通常你会写一些东西到港口,并且异步,在另一个线程上,设备将响应SerialPortEventListener . 通常,您希望在写入端口后暂停以等待响应 . 手动处理此方案的线程锁非常棘手,但使用Countdownlatch很容易 . 在你想到你可以用另一种方式做之前,要小心你从未想过的种族条件!
伪代码:
CoundDownLatch使您可以让线程等待所有其他线程完成执行 .
伪代码可以是:
如果在调用latch.countDown()之后添加一些调试,这可以帮助您更好地理解其行为 .
输出将显示Count递减 . 这'count'实际上是您启动的Runnable任务(处理器对象)的数量,其中countDown()已调用 not ,因此在调用latch.await()时阻止主线程 .
从oracle关于CountDownLatch的文档:
使用给定计数初始化
CountDownLatch
.await
方法阻塞,直到当前计数由于countDown()
方法的调用而达到零,之后所有等待的线程被释放,并且随后的任何await调用立即返回 . 这是一次性现象 - 计数无法重置 .初始化为count的
CountDownLatch
用作简单的开/关锁存器或门:所有调用等待的线程在门处等待,直到调用countDown()的线程打开它为止 .初始化为N的
CountDownLatch
可用于使一个线程等待,直到N个线程完成某个操作,或者某个操作已完成N次 .如果当前计数为零,则此方法立即返回 .
如果当前计数大于零,则递减 . 如果新计数为零,则重新启用所有等待线程以进行线程调度 .
你的例子的解释 .
latch
变量的计数设置为3您已将此共享
latch
传递给工作线程:Processor
Processor
的三个Runnable
实例已提交ExecutorService
executor
主线程(
App
)正在使用以下语句等待计数变为零Processor
线程休眠3秒然后用latch.countDown()
减少计数值由于
latch.countDown()
,第一个Process
实例将在完成后将锁存计数更改为2 .由于
latch.countDown()
,第二个Process
实例将在完成后将锁存计数更改为1 .由于
latch.countDown()
,第三个Process
实例将在完成后将锁存计数更改为0 .锁存器零计数导致主线程
App
从await
出来应用程序现在打印此输出:
Completed
正如JavaDoc(https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)中所提到的,CountDownLatch是Java 5中引入的同步辅助 . 这里的同步并不意味着限制对关键部分的访问 . 而是对不同线程的排序动作 . 通过CountDownLatch实现的同步类型与Join类似 . 假设有一个线程"M"需要等待其他工作线程"T1","T2","T3"完成其任务在Java 1.5之前,这样做的方法是,M运行以下代码
上面的代码确保线程M在T1,T2,T3完成其工作后恢复其工作 . T1,T2,T3可以按任何顺序完成工作 . 通过CountDownLatch可以实现相同的目的,其中T1,T2,T3和线程M共享相同的CountDownLatch对象 .
"M" requests:
countDownLatch.await();
其中"T1","T2","T3"确实
countDownLatch.countdown();
连接方法的一个缺点是M必须知道T1,T2,T3 . 如果稍后添加了新的工作线程T4,那么M也必须知道它 . 使用CountDownLatch可以避免这种情况 . 实施后行动顺序是[T1,T2,T3](T1,T2,T3的顺序无论如何) - > [M]
来自Java Doc的这个例子帮助我清楚地理解了这些概念:
用图解说:
显然,
CountDownLatch
允许一个线程(这里是Driver
)等到一堆运行的线程(这里是Worker
)完成它们的执行 .最佳实时countDownLatch示例在此链接中解释CountDownLatchExample