zoukankan      html  css  js  c++  java
  • CountDownLatch与join的区别和联系

    首先,我们来看一个应用场景1:

    假设一条流水线上有三个工作者:worker0,worker1,worker2。有一个任务的完成需要他们三者协作完成,worker2可以开始这个任务的前提是worker0和worker1完成了他们的工作,而worker0和worker1是可以并行他们各自的工作的。

    如果我们要编码模拟上面的场景的话,我们大概很容易就会想到可以用join来做。当在当前线程中调用某个线程 thread 的 join() 方法时,当前线程就会阻塞,直到thread 执行完成,当前线程才可以继续往下执行。补充下:join的工作原理是,不停检查thread是否存活,如果存活则让当前线程永远wait,直到thread线程终止,线程的this.notifyAll 就会被调用。

    我们首先用join来模拟这个场景:

    Worker类如下:

    [java] view plain copy
     
    1. package com.concurrent.test3;  
    2.   
    3. /** 
    4.  * 工作者类 
    5.  * @author ThinkPad 
    6.  * 
    7.  */  
    8. public class Worker extends Thread {  
    9.   
    10.     //工作者名  
    11.     private String name;  
    12.     //工作时间  
    13.     private long time;  
    14.       
    15.     public Worker(String name, long time) {  
    16.         this.name = name;  
    17.         this.time = time;  
    18.     }  
    19.       
    20.     @Override  
    21.     public void run() {  
    22.         // TODO 自动生成的方法存根  
    23.         try {  
    24.             System.out.println(name+"开始工作");  
    25.             Thread.sleep(time);  
    26.             System.out.println(name+"工作完成,耗费时间="+time);  
    27.         } catch (InterruptedException e) {  
    28.             // TODO 自动生成的 catch 块  
    29.             e.printStackTrace();  
    30.         }     
    31.     }  
    32. }  


    Test类如下:

    [java] view plain copy
     
    1. package com.concurrent.test3;  
    2.   
    3.   
    4. public class Test {  
    5.   
    6.     public static void main(String[] args) throws InterruptedException {  
    7.         // TODO 自动生成的方法存根  
    8.   
    9.         Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000));  
    10.         Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000));  
    11.         Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000));  
    12.           
    13.         worker0.start();  
    14.         worker1.start();  
    15.           
    16.         worker0.join();  
    17.         worker1.join();  
    18.         System.out.println("准备工作就绪");  
    19.           
    20.         worker2.start();          
    21.     }  
    22. }  


    运行test,观察控制台输出的顺序,我们发现这样可以满足需求,worker2确实是等worker0和worker1完成之后才开始工作的:

    worker1开始工作
    worker0开始工作
    worker1工作完成,耗费时间=3947
    worker0工作完成,耗费时间=4738
    准备工作就绪
    worker2开始工作
    worker2工作完成,耗费时间=4513

    除了用join外,用CountDownLatch 也可以完成这个需求。需要对worker做一点修改,我把它放在另一个包下:

    Worker:

    [java] view plain copy
     
    1. package com.concurrent.test4;  
    2.   
    3. import java.util.concurrent.CountDownLatch;  
    4.   
    5. /** 
    6.  * 工作者类 
    7.  * @author ThinkPad 
    8.  * 
    9.  */  
    10. public class Worker extends Thread {  
    11.   
    12.     //工作者名  
    13.         private String name;  
    14.     //工作时间  
    15.     private long time;  
    16.       
    17.     private CountDownLatch countDownLatch;  
    18.       
    19.     public Worker(String name, long time, CountDownLatch countDownLatch) {  
    20.         this.name = name;  
    21.         this.time = time;  
    22.         this.countDownLatch = countDownLatch;  
    23.     }  
    24.       
    25.     @Override  
    26.     public void run() {  
    27.         // TODO 自动生成的方法存根  
    28.         try {  
    29.             System.out.println(name+"开始工作");  
    30.             Thread.sleep(time);  
    31.             System.out.println(name+"工作完成,耗费时间="+time);  
    32.             countDownLatch.countDown();  
    33.             System.out.println("countDownLatch.getCount()="+countDownLatch.getCount());  
    34.         } catch (InterruptedException e) {  
    35.             // TODO 自动生成的 catch 块  
    36.             e.printStackTrace();  
    37.         }     
    38.     }  
    39. }  


    Test:

    [java] view plain copy
     
    1. package com.concurrent.test4;  
    2.   
    3. import java.util.concurrent.CountDownLatch;  
    4.   
    5.   
    6. public class Test {  
    7.   
    8.     public static void main(String[] args) throws InterruptedException {  
    9.         // TODO 自动生成的方法存根  
    10.   
    11.         CountDownLatch countDownLatch = new CountDownLatch(2);  
    12.         Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);  
    13.         Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);  
    14.         Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);  
    15.           
    16.         worker0.start();  
    17.         worker1.start();  
    18.           
    19.         countDownLatch.await();  
    20.         System.out.println("准备工作就绪");  
    21.         worker2.start();          
    22.     }  
    23. }  


    我们创建了一个计数器为2的 CountDownLatch ,让Worker持有这个CountDownLatch 实例,当完成自己的工作后,调用countDownLatch.countDown() 方法将计数器减1。countDownLatch.await() 方法会一直阻塞直到计数器为0,主线程才会继续往下执行。观察运行结果,发现这样也是可以的:

    worker1开始工作
    worker0开始工作
    worker0工作完成,耗费时间=3174
    countDownLatch.getCount()=1
    worker1工作完成,耗费时间=3870
    countDownLatch.getCount()=0
    准备工作就绪
    worker2开始工作
    worker2工作完成,耗费时间=3992
    countDownLatch.getCount()=0

    那么既然如此,CountDownLatch与join的区别在哪里呢?事实上在这里我们只要考虑另一种场景,就可以很清楚地看到它们的不同了。

    应用场景2:

    假设worker的工作可以分为两个阶段,work2 只需要等待work0和work1完成他们各自工作的第一个阶段之后就可以开始自己的工作了,而不是场景1中的必须等待work0和work1把他们的工作全部完成之后才能开始。

    试想下,在这种情况下,join是没办法实现这个场景的,而CountDownLatch却可以,因为它持有一个计数器,只要计数器为0,那么主线程就可以结束阻塞往下执行。我们可以在worker0和worker1完成第一阶段工作之后就把计数器减1即可,这样worker0和worker1在完成第一阶段工作之后,worker2就可以开始工作了。

    worker:

    [java] view plain copy
     
    1. package com.concurrent.test5;  
    2.   
    3. import java.util.concurrent.CountDownLatch;  
    4.   
    5. /** 
    6.  * 工作者类 
    7.  * @author ThinkPad 
    8.  * 
    9.  */  
    10. public class Worker extends Thread {  
    11.   
    12.     //工作者名  
    13.     private String name;  
    14.     //第一阶段工作时间  
    15.     private long time;  
    16.       
    17.     private CountDownLatch countDownLatch;  
    18.       
    19.     public Worker(String name, long time, CountDownLatch countDownLatch) {  
    20.         this.name = name;  
    21.         this.time = time;  
    22.         this.countDownLatch = countDownLatch;  
    23.     }  
    24.       
    25.     @Override  
    26.     public void run() {  
    27.         // TODO 自动生成的方法存根  
    28.         try {  
    29.             System.out.println(name+"开始工作");  
    30.             Thread.sleep(time);  
    31.             System.out.println(name+"第一阶段工作完成");  
    32.               
    33.             countDownLatch.countDown();  
    34.               
    35.             Thread.sleep(2000); //这里就姑且假设第二阶段工作都是要2秒完成  
    36.             System.out.println(name+"第二阶段工作完成");  
    37.             System.out.println(name+"工作完成,耗费时间="+(time+2000));  
    38.               
    39.         } catch (InterruptedException e) {  
    40.             // TODO 自动生成的 catch 块  
    41.             e.printStackTrace();  
    42.         }     
    43.     }  
    44. }  


    Test:

    [java] view plain copy
     
    1. package com.concurrent.test5;  
    2.   
    3. import java.util.concurrent.CountDownLatch;  
    4.   
    5.   
    6. public class Test {  
    7.   
    8.     public static void main(String[] args) throws InterruptedException {  
    9.         // TODO 自动生成的方法存根  
    10.   
    11.         CountDownLatch countDownLatch = new CountDownLatch(2);  
    12.         Worker worker0 = new Worker("worker0", (long) (Math.random()*2000+3000), countDownLatch);  
    13.         Worker worker1 = new Worker("worker1", (long) (Math.random()*2000+3000), countDownLatch);  
    14.         Worker worker2 = new Worker("worker2", (long) (Math.random()*2000+3000), countDownLatch);  
    15.           
    16.         worker0.start();  
    17.         worker1.start();      
    18.         countDownLatch.await();  
    19.           
    20.         System.out.println("准备工作就绪");  
    21.         worker2.start();  
    22.           
    23.     }  
    24.   
    25. }  


    观察控制台打印顺序,可以发现这种方法是可以模拟场景2的:

    worker0开始工作
    worker1开始工作
    worker1第一阶段工作完成
    worker0第一阶段工作完成
    准备工作就绪
    worker2开始工作
    worker1第二阶段工作完成
    worker1工作完成,耗费时间=5521
    worker0第二阶段工作完成
    worker0工作完成,耗费时间=6147
    worker2第一阶段工作完成
    worker2第二阶段工作完成
    worker2工作完成,耗费时间=5384

    最后,总结下CountDownLatch与join的区别:调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。

    参考:http://stackoverflow.com/questions/21808814/whats-the-difference-between-cyclicbarrier-countdownlatch-and-join-in-java

    参考链接:https://blog.csdn.net/zhutulang/article/details/48504487

  • 相关阅读:
    POJ 1015 Jury Compromise【DP】
    POJ 1661 Help Jimmy【DP】
    HDU 1074 Doing Homework【状态压缩DP】
    HDU 1024 Max Sum Plus Plus【DP,最大m子段和】
    占坑补题。。最近占的坑有点多。。。
    Codeforces 659F Polycarp and Hay【BFS】
    Codeforces 659E New Reform【DFS】
    Codeforces 659D Bicycle Race【计算几何】
    廖大python实战项目第四天
    廖大python实战项目第三天
  • 原文地址:https://www.cnblogs.com/xiohao/p/9017800.html
Copyright © 2011-2022 走看看