简体   繁体   English

Java 多线程中如何使用 CountDownLatch?

[英]How is CountDownLatch used in Java Multithreading?

Can someone help me to understand what Java CountDownLatch is and when to use it?有人可以帮助我了解 Java CountDownLatch是什么以及何时使用它吗?

I don't have a very clear idea of how this program works.我对这个程序的工作原理不是很清楚。 As I understand all three threads start at once and each Thread will call CountDownLatch after 3000ms.据我了解,所有三个线程同时启动,每个线程将在 3000 毫秒后调用 CountDownLatch。 So count down will decrement one by one.所以倒计时会一一递减。 After latch becomes zero the program prints "Completed".闩锁为零后,程序会打印“已完成”。 Maybe the way I understood is incorrect.可能我理解的方式不对。

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

}

Yes, you understood correctly.是的,你理解正确。 CountDownLatch works in latch principle, the main thread will wait until the gate is open. CountDownLatch工作原理是闩锁,主线程会一直等到门打开。 One thread waits for n threads, specified while creating the CountDownLatch .一个线程等待n 个线程,在创建CountDownLatch指定。

Any thread, usually the main thread of the application, which calls CountDownLatch.await() will wait until count reaches zero or it's interrupted by another thread.任何调用CountDownLatch.await()线程,通常是应用程序的主线程,将等待直到计数达到零或被另一个线程中断。 All other threads are required to count down by calling CountDownLatch.countDown() once they are completed or ready.一旦完成或准备好,所有其他线程都需要通过调用CountDownLatch.countDown()进行倒计时。

As soon as count reaches zero, the waiting thread continues.一旦计数达到零,等待的线程就会继续。 One of the disadvantages/advantages of CountDownLatch is that it's not reusable: once count reaches zero you cannot use CountDownLatch any more. CountDownLatch的缺点/优点之一是它不可重用:一旦计数达到零,您就不CountDownLatch使用CountDownLatch

Edit:编辑:

Use CountDownLatch when one thread (like the main thread) requires to wait for one or more threads to complete, before it can continue processing.当一个线程(如主线程)需要等待一个或多个线程完成才能继续处理时,请使用CountDownLatch

A classical example of using CountDownLatch in Java is a server side core Java application which uses services architecture, where multiple services are provided by multiple threads and the application cannot start processing until all services have started successfully.在 Java 中使用CountDownLatch一个经典示例是使用服务架构的服务器端核心 Java 应用程序,其中多个服务由多个线程提供,应用程序在所有服务启动成功后才能开始处理。

PS OP's question has a pretty straightforward example so I didn't include one. PS OP 的问题有一个非常简单的例子,所以我没有包括一个。

CountDownLatch in Java is a type of synchronizer which allows one Thread to wait for one or more Thread s before it starts processing. Java 中的CountDownLatch是一种同步器,它允许一个Thread在开始处理之前等待一个或多个Thread

CountDownLatch works on latch principle, thread will wait until gate is open. CountDownLatch工作原理是闩锁,线程会一直等到门打开。 One thread waits for n number of threads specified while creating CountDownLatch .一个线程等待创建CountDownLatch指定的n个线程。

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

Here we set the counter to 3.这里我们将计数器设置为 3。

Any thread, usually main thread of application, which calls CountDownLatch.await() will wait until count reaches zero or it's interrupted by another Thread .任何调用CountDownLatch.await()线程,通常是应用程序的主线程,都将等到 count 达到零或被另一个Thread中断。 All other threads are required to do count down by calling CountDownLatch.countDown() once they are completed or ready to the job.一旦完成或准备好工作,所有其他线程都需要通过调用CountDownLatch.countDown()进行倒计时。 as soon as count reaches zero, the Thread awaiting starts running.一旦计数达到零,等待的Thread就开始运行。

Here the count is get decremented by CountDownLatch.countDown() method.这里计数由CountDownLatch.countDown()方法递减。

The Thread which calls the await() method will wait until the initial count reaches to zero.调用await()方法的Thread将等待,直到初始计数达到零。

To make count zero other threads need to call the countDown() method.要使计数为零,其他线程需要调用countDown()方法。 Once the count become zero the thread which invoked the await() method will resume (start its execution).一旦计数变为零,调用await()方法的线程将恢复(开始执行)。

The disadvantage of CountDownLatch is that it's not reusable: once the count become zero it is no longer usable. CountDownLatch的缺点是它不可重用:一旦计数变为零,它就不再可用。

NikolaB explained it very well, However example would be helpful to understand, So here is one simple example... 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....");



     }
  }

It is used when we want to wait for more than one thread to complete its task.当我们想要等待多个线程完成其任务时使用它。 It is similar to join in threads.它类似于加入线程。

Where we can use CountDownLatch我们可以在哪里使用 CountDownLatch

Consider a scenario where we have requirement where we have three threads "A", "B" and "C" and we want to start thread "C" only when "A" and "B" threads completes or partially completes their task.考虑这样一个场景,我们要求我们有三个线程“A”、“B”和“C”,并且我们只想在“A”和“B”线程完成或部分完成它们的任务时才启动线程“C”。

It can be applied to real world IT scenario它可以应用于现实世界的 IT 场景

Consider a scenario where manager divided modules between development teams (A and B) and he wants to assign it to QA team for testing only when both the teams completes their task.考虑这样一个场景,经理在开发团队(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");
    }
}

Output of above code will be:上面代码的输出将是:

Task assigned to development team devB分配给开发团队 devB 的任务

Task assigned to development team devA分配给开发团队 devA 的任务

Task finished by development team devB开发团队 devB 完成的任务

Task finished by development team devA开发团队 devA 完成的任务

Task assigned to QA team分配给 QA 团队的任务

Task finished by QA team QA 团队完成的任务

Here await() method waits for countdownlatch flag to become 0, and countDown() method decrements countdownlatch flag by 1.这里await()方法等待 countdownlatch 标志变为 0,而countDown()方法将 countdownlatch 标志递减 1。

Limitation of JOIN: Above example can also be achieved with JOIN, but JOIN can not be used in two scenarios: JOIN的局限性:上面的例子也可以用JOIN来实现,但是JOIN不能用于两种场景:

  1. When we use ExecutorService instead of Thread class to create threads.当我们使用 ExecutorService 而不是 Thread 类来创建线程时。
  2. Modify above example where Manager wants to handover code to QA team as soon as Development completes their 80% task.修改上面的示例,其中经理希望在开发完成 80% 的任务后立即将代码移交给 QA 团队。 It means that CountDownLatch allow us to modify implementation which can be used to wait for another thread for their partial execution.这意味着 CountDownLatch 允许我们修改可用于等待另一个线程部分执行的实现。

CoundDownLatch enables you to make a thread wait till all other threads are done with their execution. CoundDownLatch 使您能够让线程等待所有其他线程完成它们的执行。

Pseudo code can be:伪代码可以是:

// 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

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.正如 JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) 中提到的,CountDownLatch 是一个同步辅助工具,在 Java 5 中引入。这里的同步没有意味着限制对临界区的访问。 But rather sequencing actions of different threads.而是对不同线程的动作进行排序。 The type of synchronization achieved through CountDownLatch is similar to that of Join.通过 CountDownLatch 实现的同步类型与 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假设有一个线程“M”需要等待其他工作线程“T1”、“T2”、“T3”来完成它的任务 在 Java 1.5 之前,可以这样做的方式是,M 运行以下代码

    T1.join();
    T2.join();
    T3.join();

The above code makes sure that thread M resumes its work after T1, T2, T3 completes its work.以上代码保证线程M在T1、T2、T3完成工作后继续工作。 T1, T2, T3 can complete their work in any order. T1、T2、T3可以任意顺序完成他们的工作。 The same can be achieved through CountDownLatch, where T1,T2, T3 and thread M share same CountDownLatch object.同样可以通过 CountDownLatch 实现,其中 T1、T2、T3 和线程 M 共享相同的 CountDownLatch 对象。
"M" requests : countDownLatch.await(); "M" 请求: countDownLatch.await();
where as "T1","T2","T3" does countDownLatch.countdown();其中 "T1","T2","T3" 确实countDownLatch.countdown();

One disadvantage with the join method is that M has to know about T1, T2, T3.连接方法的一个缺点是 M 必须知道 T1、T2、T3。 If there is a new worker thread T4 added later, then M has to be aware of it too.如果稍后添加了一个新的工作线程 T4,那么 M 也必须知道它。 This can be avoided with CountDownLatch.这可以通过 CountDownLatch 避免。 After implementation the sequence of action would be [T1,T2,T3](the order of T1,T2,T3 could be anyway) -> [M]执行后的动作顺序为 [T1,T2,T3](T1,T2,T3 的顺序可以是) -> [M]

One good example of when to use something like this is with Java Simple Serial Connector, accessing serial ports.什么时候使用这样的东西的一个很好的例子是使用 Java 简单串行连接器,访问串行端口。 Typically you'll write something to the port, and asyncronously, on another thread, the device will respond on a SerialPortEventListener.通常,您会向端口写入一些内容,并且异步地,在另一个线程上,设备将响应 SerialPortEventListener。 Typically, you'll want to pause after writing to the port to wait for the response.通常,您需要在写入端口后暂停以等待响应。 Handling the thread locks for this scenario manually is extremely tricky, but using Countdownlatch is easy.手动处理此场景的线程锁非常棘手,但使用 Countdownlatch 很容易。 Before you go thinking you can do it another way, be careful about race conditions you never thought of!!在你认为你可以用另一种方式做之前,请注意你从未想过的竞争条件!!

Pseudocode:伪代码:

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();
         }
     }
}

If you add some debug after your call to latch.countDown(), this may help you understand its behaviour better.如果您在调用 latch.countDown() 之后添加一些调试,这可能会帮助您更好地理解其行为。

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

The output will show the Count being decremented.输出将显示 Count 递减。 This 'count' is effectively the number of Runnable tasks (Processor objects) you've started against which countDown() has not been invoked and hence is blocked the main thread on its call to latch.await().这个“计数”实际上是您启动的 Runnable 任务(处理器对象)的数量,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]

From oracle documentation about CountDownLatch :来自关于CountDownLatch 的oracle 文档:

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。

A CountDownLatch is initialized with a given count. CountDownLatch使用给定的计数进行初始化。 The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately.由于调用了countDown()方法, await方法会阻塞直到当前计数达到零,之后所有等待线程都被释放,任何后续的 await 调用都会立即返回。 This is a one-shot phenomenon -- the count cannot be reset.这是一种一次性现象——计数无法重置。

A CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. CountDownLatch 是一种通用的同步工具,可用于多种目的。

A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown().计数为 1 的CountDownLatch用作简单的开/关锁存器或门:调用 await 的所有线程在门处等待,直到它被调用 countDown() 的线程打开。

A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.初始化为 N 的CountDownLatch可用于使一个线程等待,直到 N 个线程完成某个操作,或者某个操作已完成 N 次。

public void await()
           throws InterruptedException

Causes the current thread to wait until the latch has counted down to zero, unless the thread is interrupted.使当前线程等待直到闩锁倒计时为零,除非线程被中断。

If the current count is zero then this method returns immediately.如果当前计数为零,则此方法立即返回。

public void countDown()

Decrements the count of the latch, releasing all waiting threads if the count reaches zero.递减锁存器的计数,如果计数达到零,则释放所有等待的线程。

If the current count is greater than zero then it is decremented.如果当前计数大于零,则递减。 If the new count is zero then all waiting threads are re-enabled for thread scheduling purposes.如果新计数为零,则为线程调度目的重新启用所有等待线程。

Explanation of your example.解释你的例子。

  1. You have set count as 3 for latch variable您已将latch变量的计数设置为 3

     CountDownLatch latch = new CountDownLatch(3);
  2. You have passed this shared latch to Worker thread : Processor您已将此共享latch传递给工作线程: Processor

  3. Three Runnable instances of Processor have been submitted to ExecutorService executor Processor三个Runnable实例已经提交给ExecutorService executor
  4. Main thread ( App ) is waiting for count to become zero with below statement主线程( App )正在使用以下语句等待计数变为零

     latch.await();
  5. Processor thread sleeps for 3 seconds and then it decrements count value with latch.countDown() Processor线程休眠 3 秒,然后使用latch.countDown()递减计数值
  6. First Process instance will change latch count as 2 after it's completion due to latch.countDown() .由于latch.countDown()第一个Process实例在完成后会将闩锁计数更改为 2 。

  7. Second Process instance will change latch count as 1 after it's completion due to latch.countDown() .由于latch.countDown()第二个Process实例在完成后会将闩锁计数更改为 1 。

  8. Third Process instance will change latch count as 0 after it's completion due to latch.countDown() .由于latch.countDown()第三个Process实例在完成后会将闩锁计数更改为 0 。

  9. Zero count on latch causes main thread App to come out from await零计数导致主线程Appawait出来

  10. App program prints this output now : Completed应用程序现在打印此输出: Completed

This example from Java Doc helped me understand the concepts clearly: 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() { ... }
}

Visual interpretation:视觉解读:

在此处输入图片说明

Evidently, CountDownLatch allows one thread (here Driver ) to wait until a bunch of running threads (here Worker ) are done with their execution.显然, CountDownLatch允许一个线程(此处为Driver )等待直到一组正在运行的线程(此处为Worker )完成它们的执行。

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();
        }
    }
}

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

The best option is CyclicBarrier , as per https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html See:最好的选择是CyclicBarrier ,根据https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html参见:

A CountDownLatch is initialized with a given count. CountDownLatch 使用给定的计数进行初始化。 The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately.由于调用了 countDown() 方法,await 方法会阻塞直到当前计数达到零,之后所有等待线程都被释放,任何后续的 await 调用都会立即返回。 This is a one-shot phenomenon -- the count cannot be reset.这是一种一次性现象——计数无法重置。 If you need a version that resets the count, consider using a CyclicBarrier.如果您需要重置计数的版本,请考虑使用 CyclicBarrier。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM