204

有人可以帮助我了解 JavaCountDownLatch是什么以及何时使用它吗?

我对这个程序的工作原理不是很清楚。据我了解,所有三个线程同时启动,每个线程将在 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.");
    }

}
4

13 回答 13

215

是的,你理解正确。 CountDownLatch以闩锁原理工作,主线程将等待直到门打开。一个线程等待n 个线程,在创建CountDownLatch.

任何调用的线程,通常是应用程序的主线程,CountDownLatch.await()都会等到计数达到零或被另一个线程中断。CountDownLatch.countDown()一旦完成或准备好,所有其他线程都需要通过调用来倒计时。

一旦计数达到零,等待线程继续。的缺点/优点之一CountDownLatch是它不可重复使用:一旦计数达到零,您就不能再使用CountDownLatch了。

编辑:

CountDownLatch当一个线程(如主线程)需要等待一个或多个线程完成才能继续处理时使用。

在 Java 中使用的一个经典示例是CountDownLatch使用服务架构的服务器端核心 Java 应用程序,其中多个服务由多个线程提供,并且应用程序在所有服务都成功启动之前无法开始处理。

PS OP 的问题有一个非常简单的例子,所以我没有包括一个。

于 2013-07-24T07:06:46.260 回答
44

CountDownLatch在 Java 中是一种同步器,它允许在开始处理之前Thread 等待一个或多个s。Thread

CountDownLatch根据闩锁原理工作,线程将等待直到门打开。一个线程等待n创建时指定的线程数CountDownLatch

例如final CountDownLatch latch = new CountDownLatch(3);

在这里,我们将计数器设置为 3。

任何线程,通常是应用程序的主线程,调用CountDownLatch.await()将等待直到 count 达到零或被另一个Thread. CountDownLatch.countDown()一旦完成或准备好工作,所有其他线程都需要通过调用来进行倒计时。一旦计数达到零,Thread等待开始运行。

这里计数是按方法递减的CountDownLatch.countDown()

Thread调用该方法的await()将等到初始计数达到零。

要使计数为零,其他线程需要调用该countDown()方法。一旦计数变为零,调用该await()方法的线程将恢复(开始执行)。

的缺点CountDownLatch是它不可重用:一旦计数变为零,它就不再可用。

于 2014-03-19T06:54:15.863 回答
24

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....");



     }
  }
于 2014-01-06T17:28:53.440 回答
23

当我们想要等待多个线程完成其任务时使用它。它类似于加入线程。

我们可以在哪里使用 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不能用于两种场景:

  1. 当我们使用 ExecutorService 而不是 Thread 类来创建线程时。
  2. 修改上面的示例,其中经理希望在开发完成 80% 的任务后立即将代码移交给 QA 团队。这意味着 CountDownLatch 允许我们修改可用于等待另一个线程部分执行的实现。
于 2015-12-07T15:51:24.257 回答
4

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
于 2014-06-05T03:33:13.720 回答
3

如 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运行以下代码

    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]

于 2016-12-26T17:13:38.590 回答
2

何时使用此类功能的一个很好的例子是使用 Java 简单串行连接器访问串行端口。通常,您将向端口写入一些内容,并且异步地,在另一个线程上,设备将响应 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();
         }
     }
}

于 2015-05-25T02:27:23.873 回答
2

来自有关CountDownLatch的 oracle 文档:

一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。

ACountDownLatch用给定的计数初始化。由于调用该方法,这些await方法会阻塞,直到当前计数达到零countDown(),之后所有等待的线程都将被释放,任何后续的 await 调用都会立即返回。这是一次性现象——计数无法重置。

CountDownLatch 是一种多功能同步工具,可用于多种用途。

以 1 为计数的CountDownLatch初始化用作简单的开/关锁存器或门:所有调用 await 的线程在门处等待,直到它被调用 countDown() 的线程打开。

初始化为 N 可用于使一个CountDownLatch线程等待,直到 N 个线程完成某个动作,或者某个动作已完成 N 次。

public void await()
           throws InterruptedException

使当前线程等待直到锁存器倒计时到零,除非线程被中断。

如果当前计数为零,则此方法立即返回。

public void countDown()

减少锁存器的计数,如果计数达到零,则释放所有等待线程。

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

你的例子的解释。

  1. latch您已将变量的计数设置为 3

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

  3. 已提交三个Runnable实例ProcessorExecutorService executor
  4. 主线程 ( App) 正在等待计数变为零,并使用以下语句

     latch.await();  
    
  5. Processor线程休眠 3 秒,然后减少计数值latch.countDown()
  6. 由于. Process_latch.countDown()

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

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

  9. 锁存器计数为零导致主线程Appawait

  10. 应用程序现在打印此输出:Completed

于 2016-05-11T12:55:41.610 回答
2

如果您在调用latch.countDown() 之后添加一些调试,这可能会帮助您更好地理解它的行为。

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

输出将显示 Count 被递减。这个“计数”实际上是您已启动的可运行任务(处理器对象)的数量,countDown()尚未被调用,因此在调用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]
于 2016-03-18T12:06:37.233 回答
2

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)完成它们的执行。

于 2018-10-24T17:53:31.027 回答
2
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
于 2019-08-16T06:54:51.817 回答
0

最好的选择是CyclicBarrier,根据https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html 见:

CountDownLatch 使用给定的计数进行初始化。由于调用了 countDown() 方法,await 方法会一直阻塞,直到当前计数达到零,然后释放所有等待的线程,并且任何后续的 await 调用都会立即返回。这是一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用 CyclicBarrier。

于 2021-03-03T19:17:59.470 回答
0

此链接中解释了 countDownLatch 的最佳实时示例CountDownLatchExample

于 2017-09-21T06:02:02.890 回答