Java多线程中如何使用CountDownLatch?


183

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

对于这个程序的工作方式,我没有一个很清楚的想法。据我了解,所有三个线程同时启动,每个线程将在3000ms之后调用CountDownLatch。因此,倒数将逐一递减。锁存器变为零后,程序将打印“ Completed”。也许我理解的方式不正确。

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

}


8
我只是将您的问题示例代码用于android并行服务批处理,它就像一个魅力。非常感谢!
Roisgoen '16

从这里得到这段视频从2012年开始,这说明显着的相似之处在这里所示的例子。对于任何感兴趣的人,这是一个来自叫John的人的Java多线程教程系列的一部分。我喜欢约翰。强烈推荐。
埃里亚·格雷迪

Answers:


194

是的,您理解正确。 CountDownLatch按照闩锁原理工作,主线程将等待直到门打开。一个线程等待n个线程(创建时指定)CountDownLatch

任何线程(通常是应用程序的主线程)的调用CountDownLatch.await()都将等待,直到计数达到零或被另一个线程中断为止。所有其他线程在CountDownLatch.countDown()完成或准备就绪时都需要通过调用来递减计数。

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

编辑:

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

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

PS OP的问题有一个非常简单的示例,因此我没有列出。


1
感谢您的回复。您能举一个例子在哪里应用CountDown锁存器吗?
2013年

11
如何使用CountDownLatch教程在这里howtodoinjava.com/2013/07/18/...
thiagoh

1
@NikolaB但是在这个给定的示例中,我们可以通过使用join方法获得相同的结果,不是吗?
Vikas Verma 2014年

3
我认为不可重用性是一个优点:您确定没有人可以重置它或增加计数。
ataulm 2015年

3
很好的解释。但是我对此略有不同意见One thread waits for n number of threads specified while creating CountDownLatch in Java。如果您需要这样的机制,则应谨慎使用CyclicBarrier。两者之间的基本概念差异Java concurrency in Practice为:Latches are for waiting for events; barriers are for waiting for other threadscyclicBarrier.await()进入阻塞状态。
Rahul Dev Mishra

43

CountDownLatchJava中的Java是一种同步器,它允许一个Thread 人等待一个或多个Threads才开始处理。

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

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

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

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

在这里,计数递减 CountDownLatch.countDown()方法。

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

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

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


我们使用定义的new CountDownLatch(3)3个线程newFixedThreadPool 吗?
Chaklader Asfak Arefe

“开始处理之前”应该不是“继续处理之前”吗?
Maria Ines Parnisari '18

@Arefe是的,这是遍历您的代码块的线程数
Vishal Akkalkote

23

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



     }
  }

22

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

在哪里可以使用CountDownLatch

考虑一种情况,在这种情况下,我们需要拥有三个线程“ A”,“ B”和“ C”,并且仅在“ A”和“ B”线程完成或部分完成其任务时才希望启动线程“ C”。

可以应用于现实世界的IT场景

考虑这样一种情况:经理在开发团队(A和B)之间划分了模块,他只想在两个团队都完成任务时才将其分配给质量检查团队进行测试。

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完成的任务

分配给质量检查小组的任务

质量检查小组完成的任务

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

JOIN的局限性: 上面的示例也可以通过JOIN实现,但是JOIN不能在两种情况下使用:

  1. 当我们使用ExecutorService而不是Thread类创建线程时。
  2. 修改上面的示例,其中Manager要在开发完成其80%的任务后立即将代码移交给质量检查小组。这意味着CountDownLatch允许我们修改实现,该实现可用于等待其他线程的部分执行。

3

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

你可能想从代码块搬出所有的描述
保罗·罗

虽然最好的评论。我喜欢这些“直截了当”的评论,而不是理论上的解释。
renatoaraujoc

2

何时使用类似的东西的一个很好的例子是通过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

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

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

输出将显示计数递减。这种“计数”是有效的,你已经开始了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]

2

从有关CountDownLatch的 oracle文档中:

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

CountDownLatch用给定的计数初始化A。该await方法将阻塞,直到由于该countDown()方法的调用导致当前计数达到零为止,此后所有等待线程都被释放,并且随后的所有await调用都将立即返回。这是一种一次性现象-无法重置计数。

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

CountDownLatch所有线程,直到它被一个线程调用countDown方法开在栅极调用AWAIT等待():具有作为开/简单断开锁存器,或门一个发球的计数初始化。

一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。

public void await()
           throws InterruptedException

导致当前线程等待,直到锁存器递减计数到零为止,除非该线程被中断。

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

public void countDown()

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

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

您的示例的说明。

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

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

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

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

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

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

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

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


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)执行完毕。


1

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

join方法的一个缺点是M必须知道T1,T2,T3。如果以后添加了新的工作线程T4,则M也必须意识到这一点。CountDownLatch可以避免这种情况。实施后,动作顺序为[T1,T2,T3](无论如何,T1,T2,T3的顺序都可以)-> [M]



0
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();
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.