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不能用于两种情况:

  1. 当我们使用ExecutorService而不是Thread类来创build线程时。
  2. 修改上面的示例,在开发完成其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锁存器的计数,释放所有等待的线程,如果计数达到零。

如果当前计数大于零,则递减。 如果新计数为零,则所有正在等待的线程都将重新启用以进行线程调度。

你的例子的解释。

  1. 您已将计数设置为3 latchvariables

     CountDownLatch latch = new CountDownLatch(3); 
  2. 您已将此共享latch传递给工作线程: Processor

  3. Processor三个Runnable实例已经提交给ExecutorService executor
  4. 主线程( App )正在等待下面的语句成为零

      latch.await(); 
  5. Processor线程hibernate3秒,然后用latch.countDown()递减计数值
  6. 由于latch.countDown()第一个Process实例将在完成后将锁存计数更改为2。

  7. 由于latch.countDown()第二个Process实例将在完成后将锁存计数更改为1。

  8. 由于latch.countDown()第三个Process实例将在其完成后将锁存计数更改为0。

  9. locking零计数导致主线程Appawait退出

  10. 应用程序现在打印此输出: 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中解释的最佳实时示例