一、了解CountDownLatch
CountDownLatch
是java.util.concurrent
下的一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。CountDownLatch
能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。
CountDownLatch
实现基于AQS,内有一个计数器,初始化CountDownLatch
时必须调用构造函数CountDownLatch(int count)
初始化计数值,线程调用countDown()
对计数值减1,调用await()
阻塞当前线程直到计数值为0. 也可设置线程阻塞超时时间await(long timeout, TimeUnit unit)
,超时后继续执行,不再阻塞线程。
二、用法实例
用法1:一个线程等待其他线程执行完成后再执行
场景:3人到饭店吃饭,各自前往。服务员等3人都到达后再开始上菜。
顾客类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.CountDownLatch;
public class CountDownLatchUseCase {
private static class Customer implements Runnable { private CountDownLatch latch; private String name;
public Customer(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); try { Random random = new Random(); System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店"); Thread.sleep((long) (random.nextDouble() * 3000) + 1000); System.out.println(sdf.format(new Date()) + " " + name + "到了饭店"); } catch (Exception e) { e.printStackTrace(); } latch.countDown(); System.out.println(sdf.format(new Date()) + " " + name + "等待剩余人员:" + latch.getCount()); } }
private static class Waitress implements Runnable { private CountDownLatch latch; private String name;
public Waitress(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); System.out.println(sdf.format(new Date()) + " " + name + "等待顾客"); latch.await(); System.out.println(sdf.format(new Date()) + " " + name + "开始上菜"); } catch (Exception e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3);
new Thread(new Customer(latch, "张三")).start(); new Thread(new Customer(latch, "李四")).start(); new Thread(new Customer(latch, "王五")).start();
Thread.sleep(100); new Thread(new Waitress(latch, "♥小芳♥")).start(); } }
|
用法2 :设置超时时间
场景: 假如3个人去吃饭但是其中一个人发生意外(无法调用countDown()
)不去了,那服务员会一直等待不上菜,此时可以设置一个等待时间,超过这个时间不再等待。时间开发时需要注意线程阻塞问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit;
public class CountDownLatchUseCase {
private static class Customer implements Runnable { private CountDownLatch latch; private String name;
public Customer(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); try { Random random = new Random(); System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店"); Thread.sleep((long) (random.nextDouble() * 3000) + 1000); System.out.println(sdf.format(new Date()) + " " + name + "到了饭店"); } catch (Exception e) { e.printStackTrace(); } if(name.equals("张三")) { System.out.println("张三出车祸了"); return; } latch.countDown(); System.out.println(sdf.format(new Date()) + " " + name + "等待剩余人员:" + latch.getCount()); } }
private static class Waitress implements Runnable { private CountDownLatch latch; private String name;
public Waitress(CountDownLatch latch, String name) { this.latch = latch; this.name = name; }
@Override public void run() { try { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); System.out.println(sdf.format(new Date()) + " " + name + "等待顾客"); boolean result = latch.await(8, TimeUnit.SECONDS); if(result) { System.out.println(sdf.format(new Date()) + " " + name + ": 人到齐了 开始上菜"); } else { System.out.println(sdf.format(new Date()) + " " + name + ": 时间到了 开始上菜"); }
} catch (Exception e) { e.printStackTrace(); } } }
public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3);
new Thread(new Customer(latch, "张三")).start(); new Thread(new Customer(latch, "李四")).start(); new Thread(new Customer(latch, "王五")).start();
Thread.sleep(100); new Thread(new Waitress(latch, "♥小芳♥")).start(); } }
|
用法3:同时开始执行多个线程
场景: 赛跑,裁判发令后同时开始跑。所有人到达终点裁判统计成绩。
这里用到了两个CountDownLatch
,一个是裁判,发令员控制计数,计数值初始化为1,当减到0,比赛者同时开始。
另一个是比赛者,比赛者控制计数,初始化为比赛者数量,每个比赛者到达终点时通知裁判统计成绩。类似于用法1.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| import java.text.SimpleDateFormat; import java.util.Date; import java.util.Random; import java.util.concurrent.CountDownLatch;
public class CountDownLatchUseCase3 { private static class Runner implements Runnable { private CountDownLatch latchRunner; private CountDownLatch latchJudge; private String name;
public Runner(CountDownLatch latchRunner, CountDownLatch latchJudge, String name) { this.latchRunner = latchRunner; this.latchJudge = latchJudge; this.name = name; }
@Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS"); try { Random random = new Random(); System.out.println(sdf.format(new Date()) + " " + name + "准备完毕..."); latchJudge.await(); System.out.println(sdf.format(new Date()) + " " + name + "出发..."); Thread.sleep((long) (random.nextDouble() * 3000) + 1000); } catch (Exception e) { e.printStackTrace(); } System.out.println(sdf.format(new Date()) + " " + name + "到达终点!"); latchRunner.countDown(); } }
public static void main(String[] args) { CountDownLatch latchRunner = new CountDownLatch(3); CountDownLatch latchJudge = new CountDownLatch(1); System.out.println("各就各位!"); new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge, "张三")).start(); new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge,"李四")).start(); new Thread(new CountDownLatchUseCase3.Runner(latchRunner, latchJudge,"王五")).start();
try { Thread.sleep(4 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("开始"); latchJudge.countDown();
try { latchRunner.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("比赛结束"); }
}
|
三、CountDownLatch
的不足
CountDownLatch
是一次性的,计数值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch
使用完毕后,它不能再次被使用。
全文完。