Javamultithreading中如何使用CountDownLatch?
有人可以帮助我了解Java CountDownLatch
是什么以及何时使用它?
我对这个程序的工作原理并不清楚。 据我所知,所有三个线程立即开始,每个线程将在3000毫秒后调用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."); } }
是的,你理解正确。 CountDownLatch
工作在锁存原理,主线程将等到门打开。 一个线程等待在Java中创buildCountDownLatch
时指定的n个线程。
任何调用CountDownLatch.await()
线程(通常是主应用程序的主线程CountDownLatch.await()
都会等待计数到达零或被另一个线程中断。 所有其他线程都需要通过调用CountDownLatch.countDown()
来完成倒计时。
一旦计数到零,线程等待开始运行。 CountDownLatch
一个缺点/优点是一旦count达到零就不能重用,你不能再使用CountDownLatch
。
编辑:
当一个线程像主线程那样使用CountDownLatch
,需要等待一个或多个线程完成,才能开始处理。
在Java中使用CountDownLatch
典型例子是使用服务架构的任何服务器端核心Java应用程序,其中多个服务由多个线程提供,并且在所有服务已经成功启动之前,应用程序不能开始处理。
PS OP的问题有一个非常简单的例子,所以我没有包括一个。
Java中的CountDownLatch
是一种同步器,它允许一个Thread
在开始处理之前等待一个或多个Thread
。
CountDownLatch
按锁存原理工作,线程将一直等到门打开。 一个线程等待创buildCountDownLatch
指定的n
个线程数。
例如, final CountDownLatch latch = new CountDownLatch(3);
这里我们把计数器设置为3。
任何调用CountDownLatch.await()
线程(通常是应用程序的主线程CountDownLatch.await()
都将等待计数达到零,或者被另一个Thread
中断。 所有其他线程都需要通过调用CountDownLatch.countDown()
完成倒计时,一旦它们完成或准备就绪。 一旦计数到零, Thread
等待开始运行。
在这里count通过CountDownLatch.countDown()
方法减less。
调用await()
方法的Thread
将等待,直到初始计数达到零。
为了使count为零,其他线程需要调用countDown()
方法。 一旦计数变为零,调用await()
方法的线程将恢复(开始执行)。
CountDownLatch
的缺点是它不可重用:一旦计数变为零,就不再可用。
NikolaB解释得很好,但是例子会有助于理解,所以这里有一个简单的例子…
import java.util.concurrent.*; public class CountDownLatchExample { public static class ProcessThread implements Runnable { CountDownLatch latch; long workDuration; String name; public ProcessThread(String name, CountDownLatch latch, long duration){ this.name= name; this.latch = latch; this.workDuration = duration; } public void run() { try { System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds"); Thread.sleep(workDuration); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(name+ "completed its works"); //when task finished.. count down the latch count... // basically this is same as calling lock object notify(), and object here is latch latch.countDown(); } } public static void main(String[] args) { // Parent thread creating a latch object CountDownLatch latch = new CountDownLatch(3); new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs System.out.println("waiting for Children processes to complete...."); try { //current thread will get notified if all chidren's are done // and thread will resume from wait() mode. latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("All Process Completed...."); System.out.println("Parent Thread Resuming work...."); } }
当我们想要等待多个线程完成任务时使用它。 这与join线程相似。
我们可以在哪里使用CountDownLatch
考虑一个场景,我们有三个线程“A”,“B”和“C”,并且只有当“A”和“B”线程完成或部分完成任务时,才开始线程“C”。
它可以应用于现实世界的IT场景
考虑一下,pipe理员在开发团队(A和B)之间分配模块,并且只在两个团队完成任务时才想将其分配给QA团队进行testing。
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类来创build线程时。
- 修改上面的示例,在开发完成其80%的任务后,Manager希望将代码移交给QA团队。 这意味着CountDownLatch允许我们修改可用于等待另一个线程进行部分执行的实现。
Java Simple Serial Connector访问串行端口就是一个很好的例子。 通常情况下,你会写一些东西到端口,asynchronous地在另一个线程上,设备将响应一个SerialPortEventListener。 通常情况下,您需要在写入端口之后暂停以等待响应。 手动处理这个场景的线程锁是非常棘手的,但是使用Countdownlatch很容易。 在你以为你可以用另一种方式去做之前,要小心你从未想过的竞争条件!
伪代码:
CountDownLatch latch; void writeData() { latch = new CountDownLatch(1); serialPort.writeBytes(sb.toString().getBytes()) try { latch.await(4, TimeUnit.SECONDS); } catch (InterruptedException e) { } } class SerialPortReader implements SerialPortEventListener { public void serialEvent(SerialPortEvent event) { if(event.isRXCHAR()){//If data is available byte buffer[] = serialPort.readBytes(event.getEventValue()); latch.countDown(); } } }
CoundDownLatch使您可以让一个线程等待,直到所有其他线程完成执行。
伪代码可以是:
// Main thread starts // Create CountDownLatch for N threads // Create and start N threads // Main thread waits on latch // N threads completes there tasks are returns // Main thread resume execution
如果在调用latch.countDown()之后添加一些debugging,这可以帮助您更好地理解其行为。
latch.countDown(); System.out.println("DONE "+this.latch); // Add this debug
输出将显示计数递减。 这个“count”实际上是你已经启动的Runnable任务(Processor对象)的数量,它并没有被调用,因此在调用latch.await()时被阻塞了主线程。
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2] DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1] DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]
从有关CountDownLatch的 oracle文档:
同步协助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
CountDownLatch
用给定的计数初始化。 await
方法阻塞,直到当前计数由于调用countDown()
方法而达到零,在此之后所有等待的线程被释放,并且任何后续的调用立即返回。 这是一次性现象 – 计数不能被重置。
CountDownLatch是一个多function的同步工具,可用于多种目的。
CountDownLatch
初始化的计数为1用作一个简单的开/关锁存器或门:所有的线程调用等待在门口等待,直到它被调用countDown()的线程打开。
初始化为N的CountDownLatch
可用于使一个线程等待,直到N个线程完成某个动作,或者某个动作已完成N次。
public void await() throws InterruptedException
导致当前线程一直等到锁存器计数到零,除非线程中断。
如果当前计数为零,则此方法立即返回。
public void countDown()
减less锁存器的计数,释放所有等待的线程,如果计数达到零。
如果当前计数大于零,则递减。 如果新计数为零,则所有正在等待的线程都将重新启用以进行线程调度。
你的例子的解释。
-
您已将计数设置为3
latch
variablesCountDownLatch latch = new CountDownLatch(3);
-
您已将此共享
latch
传递给工作线程:Processor
-
Processor
三个Runnable
实例已经提交给ExecutorService
executor
-
主线程(
App
)正在等待下面的语句成为零latch.await();
-
Processor
线程hibernate3秒,然后用latch.countDown()
递减计数值 -
由于
latch.countDown()
第一个Process
实例将在完成后将锁存计数更改为2。 -
由于
latch.countDown()
第二个Process
实例将在完成后将锁存计数更改为1。 -
由于
latch.countDown()
第三个Process
实例将在其完成后将锁存计数更改为0。 -
locking零计数导致主线程
App
从await
退出 -
应用程序现在打印此输出:
Completed
正如JavaDoc( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html )中提到的那样,CountDownLatch是一个在Java 5中引入的同步辅助工具。这里的同步不会意味着限制访问关键部分。 而是sorting不同线程的行为。 通过CountDownLatch实现的同步types与Join类似。 假设有一个线程“M”需要等待其他工作线程“T1”,“T2”,“T3”来完成其任务在Java 1.5之前,可以这样做的方式是,M运行下面的代码T1.join(); T2.join(); T3.join();
T1.join(); T2.join(); T3.join();
上面的代码确保线程M在T1,T2,T3完成工作后恢复工作。 T1,T2,T3可以按任意顺序完成工作。 CountDownLatch可以实现同样的效果,其中T1,T2,T3和线程M共享同一个CountDownLatch对象。
“M”请求: countDownLatch.await();
where“T1”,“T2”,“T3” countDownLatch.countdown();
连接方法的一个缺点是M必须知道T1,T2,T3。 如果稍后添加新的工作线程T4,那么M也必须知道它。 这可以通过CountDownLatch来避免。 执行后的动作顺序为[T1,T2,T3](T1,T2,T3的顺序可以是任意的) – > [M]
在此链接CountDownLatchExample中解释的最佳实时示例