有人能帮助我理解什么是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.");
}
}
As mentioned in JavaDoc (https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html), CountDownLatch is a synchronization aid, introduced in Java 5. Here the synchronization does not mean restricting access to a critical section. But rather sequencing actions of different threads.
The type of synchronization achieved through CountDownLatch is similar to that of Join.
Assume that there is a thread "M" which needs to wait for other worker threads "T1", "T2", "T3" to complete its tasks
Prior to Java 1.5, the way this can be done is, M running the following code
T1.join();
T2.join();
T3.join();
上面的代码确保线程M在T1、T2、T3完成它的工作之后恢复它的工作。T1, T2, T3可以按任意顺序完成它们的工作。
同样可以通过CountDownLatch实现,其中T1、T2、T3和线程M共享相同的CountDownLatch对象。
"M"请求:countDownLatch.await();
其中“T1”,“T2”,“T3”是countDownLatch.countdown();
连接方法的一个缺点是M必须知道T1、T2、T3。如果后来添加了一个新的工作线程T4,那么M也必须知道它。使用CountDownLatch可以避免这种情况。
实现后的动作顺序为[T1,T2,T3](T1,T2,T3的顺序可以任意)-> [M]
这个来自Java Doc的例子帮助我清楚地理解了这些概念:
class Driver { // ...
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
doSomethingElse(); // don't let run yet
startSignal.countDown(); // let all threads proceed
doSomethingElse();
doneSignal.await(); // wait for all to finish
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
视觉解释:
显然,CountDownLatch允许一个线程(这里是Driver)等待一堆正在运行的线程(这里是Worker)完成它们的执行。
来自oracle关于CountDownLatch的文档:
一种同步辅助工具,允许一个或多个线程等待在其他线程中执行的一组操作完成。
CountDownLatch使用给定的计数进行初始化。由于调用了countDown()方法,await方法阻塞直到当前计数为零,在此之后所有等待的线程都被释放,并且await的任何后续调用立即返回。这是一个一次性现象——计数不能重置。
CountDownLatch是一种通用的同步工具,可用于许多目的。
初始化为1的CountDownLatch用作简单的开/关锁存器或门:所有调用await的线程都在门处等待,直到由调用countDown()的线程打开。
初始化为N的CountDownLatch可用于使一个线程等待到N个线程完成某个操作,或者某个操作已完成N次。
public void await()
throws InterruptedException
导致当前线程等待,直到锁存器计数到零,除非线程被中断。
如果当前计数为零,则此方法立即返回。
public void countDown()
减少锁存器的计数,如果计数为零,则释放所有等待线程。
如果当前计数大于零,则递减。如果新的计数为零,则所有等待的线程都将重新启用以进行线程调度。
解释你的例子。
You have set count as 3 for latch variable
CountDownLatch latch = new CountDownLatch(3);
You have passed this shared latch to Worker thread : Processor
Three Runnable instances of Processor have been submitted to ExecutorService executor
Main thread ( App ) is waiting for count to become zero with below statement
latch.await();
Processor thread sleeps for 3 seconds and then it decrements count value with latch.countDown()
First Process instance will change latch count as 2 after it's completion due to latch.countDown().
Second Process instance will change latch count as 1 after it's completion due to latch.countDown().
Third Process instance will change latch count as 0 after it's completion due to latch.countDown().
Zero count on latch causes main thread App to come out from await
App program prints this output now : Completed
是的,你理解对了。
CountDownLatch的工作原理是闩锁,主线程将等待门被打开。一个线程等待n个线程,这是在创建CountDownLatch时指定的。
任何调用CountDownLatch.await()的线程(通常是应用程序的主线程)都将等待,直到count达到零或被另一个线程中断。所有其他线程都需要在完成或准备就绪后调用CountDownLatch.countDown()进行倒计时。
只要count达到0,等待线程就会继续。CountDownLatch的一个缺点/优点是它不可重用:一旦count达到0,就不能再使用CountDownLatch了。
编辑:
当一个线程(如主线程)需要等待一个或多个线程完成后才能继续处理时,使用CountDownLatch。
在Java中使用CountDownLatch的一个经典示例是一个服务器端核心Java应用程序,该应用程序使用服务体系结构,其中多个服务由多个线程提供,在所有服务成功启动之前应用程序不能开始处理。
注:
OP的问题有一个非常简单的例子,所以我没有包括一个。
当我们想要等待多个线程完成其任务时,就会使用它。它类似于线程连接。
我们可以在哪里使用CountDownLatch
考虑这样一个场景:我们有三个线程“a”、“B”和“C”,我们希望只有在“a”和“B”线程完成或部分完成它们的任务时才启动线程“C”。
它可以应用于真实的It场景
考虑这样一个场景,经理将模块划分到开发团队(a和B)之间,他希望仅在两个团队都完成任务时将模块分配给QA团队进行测试。
public class Manager {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
teamDevA.start();
teamDevB.start();
countDownLatch.await();
MyQATeam qa = new MyQATeam();
qa.start();
}
}
class MyDevTeam extends Thread {
CountDownLatch countDownLatch;
public MyDevTeam (CountDownLatch countDownLatch, String name) {
super(name);
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("Task assigned to development team " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by development team " + Thread.currentThread().getName());
this.countDownLatch.countDown();
}
}
class MyQATeam extends Thread {
@Override
public void run() {
System.out.println("Task assigned to QA team");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by QA team");
}
}
以上代码的输出为:
分配给开发团队devB的任务
分配给开发团队devA的任务
任务由开发团队devB完成
由开发团队devA完成的任务
分配给QA团队的任务
QA团队完成的任务
这里await()方法等待countdownlatch标志变为0,而countDown()方法将countdownlatch标志减1。
JOIN的限制:
上面的例子也可以通过JOIN实现,但是JOIN不能在两种情况下使用:
当我们使用ExecutorService而不是Thread类创建线程时。
修改上面的例子,经理希望在开发完成80%的任务后立即将代码移交给QA团队。这意味着CountDownLatch允许我们修改实现,可以用来等待另一个线程的部分执行。