有人能帮助我理解什么是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.");
    }

}

当前回答

当我们想要等待多个线程完成其任务时,就会使用它。它类似于线程连接。

我们可以在哪里使用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不能在两种情况下使用:

当我们使用ExecutorService而不是Thread类创建线程时。 修改上面的例子,经理希望在开发完成80%的任务后立即将代码移交给QA团队。这意味着CountDownLatch允许我们修改实现,可以用来等待另一个线程的部分执行。

其他回答

是的,你理解对了。 CountDownLatch的工作原理是闩锁,主线程将等待门被打开。一个线程等待n个线程,这是在创建CountDownLatch时指定的。

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

只要count达到0,等待线程就会继续。CountDownLatch的一个缺点/优点是它不可重用:一旦count达到0,就不能再使用CountDownLatch了。

编辑:

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

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

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

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



     }
  }

当我们想要等待多个线程完成其任务时,就会使用它。它类似于线程连接。

我们可以在哪里使用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不能在两种情况下使用:

当我们使用ExecutorService而不是Thread类创建线程时。 修改上面的例子,经理希望在开发完成80%的任务后立即将代码移交给QA团队。这意味着CountDownLatch允许我们修改实现,可以用来等待另一个线程的部分执行。

如果你在调用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]

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