2013-07-24 64 views
122

有人可以帮助我了解Java CountDownLatch是什么以及何时使用它?Java多线程中如何使用CountDownLatch?

我对这个程序的工作原理没有很清楚的想法。据我所知,所有三个线程立即开始,每个线程将在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."); 
    } 

} 
+0

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html –

+4

我刚刚使用您的问题示例代码进行android并行服务批处理,它的工作方式就像一个魅力。非常感谢! – Roisgoen

回答

144

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

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

一旦计数到零,线程等待开始运行。 CountDownLatch的缺点/优势之一在于,一旦计数达到零就不能重复使用,因此不能再使用CountDownLatch

编辑:

使用CountDownLatch当一个线程像主线程,需要等待一个或多个线程来完成,然后才可以开始处理。

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

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

+0

谢谢你的回复。你能给我一个应用CountDown闩锁的例子吗? – amal

+9

如何使用CountDownLatch的教程在这里http://howtodoinjava.com/2013/07/18/when-to-use-countdownlatch-java-concurrency-example-tutorial/ – thiagoh

+1

@NikolaB但在这个给出的例子中,我们可以通过使用连接方法实现相同的结果不是吗? –

17

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



    } 
    } 
+0

你的例子清楚地说明了这件事的作用。 –

29

在Java中CountDownLatch是一种同步的,它允许一个Thread等待一个或更多Thread s,然后开始处理。

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

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

这里我们设置了计数器3

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

这里计数减少CountDownLatch.countDown()方法。

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

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

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

+4

水晶般清晰的解释 –

+0

我们是否使用'new CountDownLatch(3)',因为我们有3个线程来自'newFixedThreadPool'定义的? – Arefe

+0

应该“在它开始处理之前”是“在它继续处理之前”吗? –

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 
+0

您可能希望从代码块 –

+0

中移除所有描述尽管最好的评论。我喜欢这些“到点”的评论,而不是理论上的解释。 – Renatinn

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

15

当我们要等待一个以上的线程来完成它的任务时使用。这与加入线程相似。

我们在哪里可以使用CountDownLatch

考虑这样一个场景,我们有一定的要求,我们有三个线程“A”,“B”和“C”,我们要启动线程“C”,只有当“ “和”B“线程完成或部分完成其任务。

它可以应用到现实世界的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"); 
    } 
} 

输出将是:

任务分配给开发团队发展局

任务分配给开发团队德瓦

任务由开发团队发展局完成

任务由开发团队完成devA

任务分配到质量检查小组

任务由QA队完成

这里等待()方法等待countdownlatch标志成为0,并且COUNTDOWN()方法通过1​​

递减countdownlatch标志的

限制JOIN: 上面的例子也可以用JOIN来实现,但是JOIN不能在两种情况中使用:

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

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

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

输出将显示Count递减。这个“count”实际上是您已经启动的可运行任务(处理器对象)的数量,countDown()具有而非已被调用,因此在调用latch.await()时被阻塞主线程。

DONE [email protected][Count = 2] 
DONE [email protected][Count = 1] 
DONE [email protected][Count = 0] 
2

从Oracle的文档约CountDownLatch

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

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

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

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

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

public void await() 
      throws InterruptedException 

造成当前线程在等待,直到锁存器倒计数至零,除非线程被中断。

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

public void countDown() 

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

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

你的例子的解释。

  1. 你已设定次数为3 latch变量

    CountDownLatch latch = new CountDownLatch(3); 
    
  2. 你已经过了这个共享latch到工作线程:的ProcessorProcessor

  3. Runnable情况已提交给ExecutorServiceexecutor
  4. Main th阅读(App)正在等待数成为零,下面的语句

    latch.await(); 
    
  5. Processor线程休眠3秒钟,然后递减与latch.countDown()
  6. 首先Process实例计数值将改变锁存计为2它的后由于latch.countDown()而完成。

  7. 第二个Process由于latch.countDown(),它会在完成后将锁存计数更改为1。

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

  9. 零计数使主线程App从出来await

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

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

一个缺点是,M具有了解T1,T2,T3。如果稍后添加新的工作线程T4,那么M也必须注意到它。这可以通过CountDownLatch来避免。 实施后动作的顺序是[T1,T2,T3(T1的顺序,T2,T3可以是反正) - > [M]