一、介绍
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
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()方法来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。内部使用AQS的getState方法获取计数器,如果计数器值不等于0,则会以自旋方式会尝试一直去获取同步状态。
• CountDownLatch 提供countDown() 方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态。
三、案例
在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之后则接力运动员开始起跑,直至到达终点。
标签:CountDownLatch,运动员,线程,接力,使用,就位,起点 From: https://www.cnblogs.com/zwh0910/p/17014259.html