首页 文章

CountDownLatch如何在Java多线程中使用?

提问于
浏览
149

有人可以帮我理解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.");
    }

}

11 回答

  • 1

    是的,你理解正确 . CountDownLatch 在锁存原理中工作,主线程将等到门打开 . 一个线程等待创建 CountDownLatch 时指定的n个线程 .

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

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

    Edit:

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

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

    附: OP的问题有一个非常简单的例子,所以我没有包含一个 .

  • 0

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

    CountDownLatch 在锁存原理上工作,线程将一直等到门打开 . 一个线程在创建 CountDownLatch 时等待 n 指定的线程数 .

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

    这里我们将计数器设置为3 .

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

    这里的计数通过 CountDownLatch.countDown() 方法递减 .

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

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

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

  • 2

    尼古拉B解释得非常好,但是例子有助于理解,所以这里有一个简单的例子......

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

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

    Where we can use CountDownLatch

    考虑一个我们有需求的场景,我们有三个线程“A”,“B”和“C”,我们只想在“A”和“B”线程完成或部分完成其任务时启动线程“C” .

    It can be applied to real world IT scenario

    考虑一种情况,管理者在开发团队(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:

    分配给开发团队devB的任务

    分配给开发团队devA的任务

    任务由开发团队devB完成

    任务由开发团队devA完成

    任务分配给QA团队

    任务由QA团队完成

    这里 await() 方法等待countdownlatch标志变为0, countDown() 方法将countdownlatch标志减1 .

    Limitation of JOIN: 以上示例也可以通过JOIN实现,但JOIN不能在两种情况下使用:

    • 当我们使用ExecutorService而不是Thread类来创建线程时 .

    • 修改以上示例,其中Manager在开发完成80%任务后立即将代码切换到QA团队 . 这意味着CountDownLatch允许我们修改可用于等待另一个线程进行部分执行的实现 .

  • 18

    什么时候使用这样的东西的一个很好的例子是Java Simple Serial Connector,访问串行端口 . 通常你会写一些东西到港口,并且异步,在另一个线程上,设备将响应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();
             }
         }
    }
    
  • 2

    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
    
  • 34

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

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

    输出将显示Count递减 . 这'count'实际上是您启动的Runnable任务(处理器对象)的数量,其中countDown()已调用 not ,因此在调用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]
    
  • 2

    从oracle关于CountDownLatch的文档:

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

    使用给定计数初始化 CountDownLatch . await 方法阻塞,直到当前计数由于 countDown() 方法的调用而达到零,之后所有等待的线程被释放,并且随后的任何await调用立即返回 . 这是一次性现象 - 计数无法重置 .

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

    初始化为count的 CountDownLatch 用作简单的开/关锁存器或门:所有调用等待的线程在门处等待,直到调用countDown()的线程打开它为止 .

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

    public void await()
               throws InterruptedException
    

    使当前线程等到锁存器倒计数到零,除非线程被中断 .

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

    public void countDown()
    

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

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

    你的例子的解释 .

    • 您已将 latch 变量的计数设置为3
    CountDownLatch latch = new CountDownLatch(3);
    
    • 您已将此共享 latch 传递给工作线程: Processor

    • Processor 的三个 Runnable 实例已提交 ExecutorService executor

    • 主线程( App )正在使用以下语句等待计数变为零

    latch.await();
    
    • Processor 线程休眠3秒然后用 latch.countDown() 减少计数值

    • 由于 latch.countDown() ,第一个 Process 实例将在完成后将锁存计数更改为2 .

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

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

    • 锁存器零计数导致主线程 Appawait 出来

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

  • 17

    正如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" requests: countDownLatch.await();
    其中"T1","T2","T3"确实 countDownLatch.countdown();

    连接方法的一个缺点是M必须知道T1,T2,T3 . 如果稍后添加了新的工作线程T4,那么M也必须知道它 . 使用CountDownLatch可以避免这种情况 . 实施后行动顺序是[T1,T2,T3](T1,T2,T3的顺序无论如何) - > [M]

  • 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() { ... }
    }
    

    用图解说:

    enter image description here

    显然, CountDownLatch 允许一个线程(这里是 Driver )等到一堆运行的线程(这里是 Worker )完成它们的执行 .

  • 1

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

相关问题