一、介绍
CountDownLatch是一个计数的闭锁,作用与CyclicBarrier有点儿相似。
在 API中是这样描述的:
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。
这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
• CountDownLatch :一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;
• CyclicBarrier :多个线程互相等待,直到到达同一个同步点,再继续一起执行。
对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后就可以恢复等待的线程继续执行了。
二、实现分析
CountDownLatch结构如下
通过上面的结构图我们可以看到, CountDownLatch内部依赖Sync实现,而Sync继承AQS。
CountDownLatch仅提供了一个构造方法:
CountDownLatch(int count) : 构造一个用给定计数初始化的 CountDownLatch,count为线程数,表示等待线程数量。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
sync 为CountDownLatch的一个内部类,通过这个内部类Sync可以知道CountDownLatch是采用共享锁来实现的。最长用的两个方法是await()和countDown():
• CountDownLatch 提供await()方法来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断,当计数器为0时唤醒等待的线程。内部使用AQS的getState方法获取计数器,如果计数器值不等于0,则会以自旋方式会尝试一直去获取同步状态。
• CountDownLatch 提供countDown() 方法递减锁存器的计数,表示当前线程执行完毕,如果计数到达零,则释放所有等待的线程。内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态。
三、案例
案例1:
线程类MyThread
import java.util.Random; import java.util.concurrent.CountDownLatch; public class MyThread extends Thread { private final CountDownLatch latch; private final Random random = new Random(); public MyThread(String name, CountDownLatch latch) { super(name); this.latch = latch; } @Override public void run() { try { Thread.sleep(random.nextInt(2000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " - 执行完毕"); // latch计数减一 latch.countDown(); } }
要创建CountDownLatch对象并传递给线程
Main类
import java.util.concurrent.CountDownLatch; public class Main { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(5); for (int i = 0; i < 4; i++) { new MyThread("线程" + (i + 1), latch).start(); } // main线程等待 latch.await(); System.out.println("main线程执行结束"); } }
由于CountDownLatch的初始计数为5,就必须调用countDown()5次,否则latch.await()一直处于阻塞状态。打印如下:
线程1 - 执行完毕 线程2 - 执行完毕 线程3 - 执行完毕 线程4 - 执行完毕
此时修改线程个数为5即可
import java.util.concurrent.CountDownLatch; public class Main { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(5); for (int i = 0; i < 5; i++) { new MyThread("线程" + (i + 1), latch).start(); } // 调用await()方法时,main线程等待 latch.await(); System.out.println("main线程执行结束"); } }
打印如下:
线程5 - 执行完毕 线程1 - 执行完毕 线程4 - 执行完毕 线程3 - 执行完毕 线程2 - 执行完毕 main线程执行结束
案例2:妈妈等三个孩子吃完后,再收拾碗筷
孩子1线程类ChildThread1
import java.util.concurrent.CountDownLatch; public class ChildThread1 extends Thread { private CountDownLatch countDownLatch; public ChildThread1(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { //1.吃饺子 for (int i = 1; i <= 10; i++) { System.out.println(getName() + "在吃第" + i + "个饺子"); } //2.吃完说一声 //每一次countDown方法的时候,就让计数器-1 countDownLatch.countDown(); } }
孩子2线程类ChildThread2
import java.util.concurrent.CountDownLatch; public class ChildThread2 extends Thread { private CountDownLatch countDownLatch; public ChildThread2(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { //1.吃饺子 for (int i = 1; i <= 15; i++) { System.out.println(getName() + "在吃第" + i + "个饺子"); } //2.吃完说一声 //每一次countDown方法的时候,就让计数器-1 countDownLatch.countDown(); } }
孩子3线程类ChildThread3
import java.util.concurrent.CountDownLatch; public class ChildThread3 extends Thread { private CountDownLatch countDownLatch; public ChildThread3(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { //1.吃饺子 for (int i = 1; i <= 20; i++) { System.out.println(getName() + "在吃第" + i + "个饺子"); } //2.吃完说一声 //每一次countDown方法的时候,就让计数器-1 countDownLatch.countDown(); } }
妈妈线程类
import java.util.concurrent.CountDownLatch; public class MotherThread extends Thread { private CountDownLatch countDownLatch; public MotherThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { //1.等待 try { //当计数器变成0的时候,会自动唤醒这里等待的线程。 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } //2.收拾碗筷 System.out.println("妈妈在收拾碗筷"); } }
测试类
import java.util.concurrent.CountDownLatch; public class MyCountDownLatchDemo { public static void main(String[] args) { //1.创建CountDownLatch的对象,需要传递给四个线程。 //在底层就定义了一个计数器,此时计数器的值就是3 CountDownLatch countDownLatch = new CountDownLatch(3); //2.创建四个线程对象并开启他们。 MotherThread motherThread = new MotherThread(countDownLatch); motherThread.start(); ChildThread1 t1 = new ChildThread1(countDownLatch); t1.setName("小明"); ChildThread2 t2 = new ChildThread2(countDownLatch); t2.setName("小红"); ChildThread3 t3 = new ChildThread3(countDownLatch); t3.setName("小刚"); t1.start(); t2.start(); t3.start(); } }
打印结果:
小红在吃第1个饺子
小刚在吃第1个饺子
小刚在吃第2个饺子
小刚在吃第3个饺子
小刚在吃第4个饺子
小刚在吃第5个饺子
小刚在吃第6个饺子
小刚在吃第7个饺子
小刚在吃第8个饺子
小刚在吃第9个饺子
小刚在吃第10个饺子
小刚在吃第11个饺子
小刚在吃第12个饺子
小刚在吃第13个饺子
小刚在吃第14个饺子
小刚在吃第15个饺子
小刚在吃第16个饺子
小刚在吃第17个饺子
小刚在吃第18个饺子
小刚在吃第19个饺子
小刚在吃第20个饺子
小红在吃第2个饺子
小红在吃第3个饺子
小红在吃第4个饺子
小红在吃第5个饺子
小红在吃第6个饺子
小红在吃第7个饺子
小红在吃第8个饺子
小红在吃第9个饺子
小红在吃第10个饺子
小红在吃第11个饺子
小红在吃第12个饺子
小红在吃第13个饺子
小红在吃第14个饺子
小红在吃第15个饺子
小明在吃第1个饺子
小明在吃第2个饺子
小明在吃第3个饺子
小明在吃第4个饺子
小明在吃第5个饺子
小明在吃第6个饺子
小明在吃第7个饺子
小明在吃第8个饺子
小明在吃第9个饺子
小明在吃第10个饺子
妈妈在收拾碗筷
案例3:
ClickNumber
import java.util.concurrent.atomic.LongAdder; public class ClickNumber{ LongAdder longAdder = new LongAdder(); public void add_LongAdder(){ longAdder.increment(); } }
测试类
import java.util.concurrent.CountDownLatch; public class LongAdderCalcDemo { public static final int SIZE_THREAD = 50; // 50个线程 public static final int _1w = 10000; public static void main(String[] args) throws InterruptedException { ClickNumber clickNumber = new ClickNumber(); long startTime = System.currentTimeMillis(); long endTime = System.currentTimeMillis(); // CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。 // 当计数器值到达0时,它表示所有的线程已经完成了任务,然后就可以恢复等待的线程继续执行了。 CountDownLatch latch_LongAdder = new CountDownLatch(SIZE_THREAD); startTime = System.currentTimeMillis(); for (int i = 1; i <= SIZE_THREAD; i++) { new Thread(()->{ try { for (int j = 1; j <= 100*_1w; j++) { clickNumber.add_LongAdder(); } } catch (Exception e) { e.printStackTrace(); } finally { latch_LongAdder.countDown(); // 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 } },String.valueOf(i)).start(); } latch_LongAdder.await(); // 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 endTime = System.currentTimeMillis(); System.out.println("LongAdder花费时间:"+ (endTime-startTime)+" 数值为:"+clickNumber.longAdder.longValue()); } }
结果为:LongAdder花费时间:143 数值为:50000000
如果未使用CountDownLatch,main线程启动了50个worker线程,main线程执行完之后就退出了,此时50个worker线程还在执行。如果我们想让主线程等待,让50个worker线程执行完之后main线程才退出,可以使用CountDownLatch实现主线程等待50个worker线程执行完之后才退出。
案例4:
在CyclicBarrier应用场景之上进行修改,添加接力运动员。
起点运动员应该等其他起点运动员准备好才可以起跑(CyclicBarrier)。
接力运动员不需要关心其他人,只需和自己有关的起点运动员到接力点即可开跑(CountDownLatch)。
public class Demo2CountDownLatch { public static void main(String[] args) { // 拦截线程的数量为5,即5个起点运动员 CyclicBarrier cyclicBarrier = new CyclicBarrier(5); List<Thread> threadList = new ArrayList<>(); for (int i = 0; i < 5; i++) { CountDownLatch countDownLatch = new CountDownLatch(1); // 5个起点运动员 Thread t1 = new Thread(new Athlete(cyclicBarrier, countDownLatch,"起点运动员" + i)); // 5个接力运动员 Thread t2 = new Thread(new Athlete(countDownLatch, "接力运动员" + i)); threadList.add(t1); threadList.add(t2); } for (Thread t : threadList) { t.start(); } } static class Athlete implements Runnable { private CyclicBarrier cyclicBarrier; private String name; CountDownLatch countDownLatch; // 起点运动员,需要CyclicBarrier来等待所有运动员就位 public Athlete(CyclicBarrier cyclicBarrier, CountDownLatch countDownLatch, String name) { this.cyclicBarrier = cyclicBarrier; this.countDownLatch = countDownLatch; this.name = name; } // 接力运动员 public Athlete(CountDownLatch countDownLatch, String name) { this.countDownLatch = countDownLatch; this.name = name; } @Override public void run() { // 判断是否是起点运动员,不为Null说明是起点运动员 if (cyclicBarrier != null) { System.out.println(name + "就位"); // 一开始所有5个起点运动员各就各位 try { cyclicBarrier.await(); // 当最后一个起点运动员就位后就开始起跑 System.out.println(name + "到达交接点。"); // 已经到达交接点,到达交接点后,计数到达0 countDownLatch.countDown(); } catch (Exception e) { } } //判断是否是接力运动员 if (cyclicBarrier == null) { // 如果为空说明是接力运动员 System.out.println(name + "就位"); try { // 使当前线程在锁存器倒计数至零之前一直等待,到达0之后则释放所有等待的线程 countDownLatch.await(); System.out.println(name + "到达终点。"); } catch (Exception e) { } } } } }
结果如下:
起点运动员1就位
接力运动员1就位
起点运动员3就位
接力运动员3就位
起点运动员0就位
接力运动员0就位
起点运动员2就位
接力运动员2就位
起点运动员4就位
起点运动员4到达交接点。
接力运动员4就位
接力运动员4到达终点。
起点运动员3到达交接点。
起点运动员1到达交接点。
接力运动员3到达终点。
起点运动员2到达交接点。
起点运动员0到达交接点。
接力运动员2到达终点。
接力运动员1到达终点。
接力运动员0到达终点。
一开始,5个起点运动员就位。当最后一个起点运动员就位后,就开始起跑。当起点运动员到达接力点时,计数减1为0。由于接力运动员在锁存器倒计数至零之前一直等待,而计数到达0之后则接力运动员开始起跑,直至到达终点。