CountDownLatch
Latch门栓,也就是有"门锁"的功能;
当门没有打开时,N个人是不能进入屋内的,也就是N个线程是不能继续往下运行的,支持这样的特性可以控制线程执行任务的时机,使线程以"组团"的方式一起执行任务。
CountDownLatch所提供的功能是判断count计数不为0时,则当前线程呈wait状态,也就是在屏障处等待。
CountDownLatch也是一个同步功能的辅助类,使用效果是给定一个计数,当使用这个CountDownLatch类的线程计数不为0时,则呈wait状态,如果为0时则继续运行。
实现等待与继续运行的效果分别需要使用await()和countDown()方法来进行。
调用await()方法时判断计数是否为0,如果不为0则呈等待状态。
其他线程可以调用countDown()方法将计数减1,当计数减到0时,呈等待的线程继续运行。
而方法getCount()就是获得当前的计数个数。
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。
(例如:应用程序的主线程希望在 负责启动框架服务的线程已经启动所有的框架服务之后 再执行)
CountDownLatch的作用与Thread.join()方法类似,可用于一组线程和另外一组线程的协作。
await():调用此方法会一直阻塞当前线程,直到计时器为0。
countDown():当线程调用此方法,计数减一。
场景:
运动会赛跑,裁判员要等所有的运动员到达起跑线才开始准备。
初始化CountDownLatch,同时设置计数器的数量(阀值),一个运动员到达,计数器减1,当计数器为0,说明所有运动员到场,
于是看是运行后面的内容
初步使用
这个Demo实现 当CountDownLatch的计数不为0时,线程呈阻塞状态。
public class MyService {
private CountDownLatch down = new CountDownLatch(1);
public void testMethod() {
try {
System.out.println("A");
down.await();
System.out.println("B");
} catch (Exception e) {
e.printStackTrace();
}
}
public void downMethod() {
System.out.println("X");
down.countDown();
}
}
public class MyThread extends Thread {
private MyService service;
public MyThread(MyService service) {
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run {
public static void main(String[] args) throws Exception {
MyService service = new MyService();
MyThread t = new MyThread(service);
t.start();
Thread.sleep(2000);
service.downMethod();
}
}
输出结果:
A
X
B
我们来分析下代码:
- main方法启动,执行Thread线程,线程运行MyService类中的testMethod()方法,输出"A"。
- 执行到down.await();时,线程阻塞,开始等count计数减少为0。
- 上面执行的同时,main方法接着往下走,睡眠了两秒,然后调用MyService类的downMethod()方法
- 输出"X"
- 这时候,执行down.countDown(),CountDownLatch的计数器减1,于是CountDownLatch计数器变为0。
- 于是,阻塞在testMethod()方法中的Thread线程 输出"B"
第二个例子
模拟一个栗子:
裁判员要等到所有运动员全部都集中后,才继续往下运行。
论证多个线程 与 同步点之间 阻塞的特点,线程必须都到达同步点之后才可以继续向下运行。
public class MyThread extends Thread {
private CountDownLatch maxRunner;
public MyThread(CountDownLatch maxRunner) {
this.maxRunner = maxRunner;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(2000);
maxRunner.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) throws Exception {
CountDownLatch maxRuner = new CountDownLatch(10);
MyThread[] tArr = new MyThread[Integer.parseInt("" + maxRuner.getCount())];
for (int i = 0; i < tArr.length; i++) {
tArr[i] = new MyThread(maxRuner);
tArr[i].setName("线程" + (i + 1));
tArr[i].start();
}
maxRuner.await();
System.out.println("都回来了");
}
}
输出结果:
线程1
线程2
线程3
线程4
线程6
线程5
线程8
线程7
线程9
线程10
都回来了
让我们来捋一下代码:
- 我们将计数器设为10,然后创建10个线程。
- 迭代线程,每个线程同时执行,在线程中我们将计数器减1,并且睡两秒
- 主线程要等计数器为0的时候,才能继续往下走
- 最后10个线程,全部循环完成,计数器归零,然后输出"都回来了"
第三个栗子
场景:裁判员要等待所有运动员各就各位以后,再开始比赛
public class MyService {
private CountDownLatch down = new CountDownLatch(1);
public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + " 准备");
down.await();
System.out.println(Thread.currentThread().getName() + " 结束");
} catch (Exception e) {
e.printStackTrace();
}
}
public void downMethod() {
System.out.println("开始");
down.countDown();
}
}
public class MyThread extends Thread {
private MyService service;
public MyThread(MyService service) {
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run {
public static void main(String[] args) throws Exception {
MyService service = new MyService();
MyThread[] tArr = new MyThread[10];
for (int i = 0; i < 10; i++) {
tArr[i] = new MyThread(service);
tArr[i].setName("线程 " + (i + 1));
tArr[i].start();
}
Thread.sleep(2000);
service.downMethod();
}
}
输出结果:
线程 1 准备
线程 3 准备
线程 4 准备
线程 5 准备
线程 2 准备
线程 7 准备
线程 8 准备
线程 6 准备
线程 9 准备
线程 10 准备
开始
线程 1 结束
线程 3 结束
线程 4 结束
线程 5 结束
线程 2 结束
线程 7 结束
线程 8 结束
线程 6 结束
线程 10 结束
线程 9 结束
捋一下程序:
- Run类中创建一个MyService对象,这时候,计数器中数量为1
- 然后创建10个线程,同时启动这10个线程。
- 每个线程执行输出"准备"后,因为await()方法,开始阻塞。
- 10个线程,全部进入阻塞状态。
- 主线程继续往下走,睡两秒,然后执行downMethod()方法。
- 这时候,计数器减一。
- 接下来所有阻塞的线程,输出"结束"
这个Demo有个问题,虽然运行成功,但不能保证在main主线程执行了service.downMethod()时,所有的工作线程都呈wait状态,因为某些线程有可能准备的时间较长,可能耗时超过两秒,这时,如果在第两秒调用service.downMethod()方法,就达不到"唤醒所有线程"
继续往下运行的目的了,也就是说,裁判员没有等全部的运动员到来时,就让发令枪响起来开始比赛了。
下面我们将这个Demo修改一下。
第四个栗子
我们使用CountDownLatch类来实现"所有线程"呈wait后再同一唤醒的效果,此Demo大量使用CountDownLatch类来实现业务要求的同步效果。
public class MyThread extends Thread {
// 裁判等待所有运动员到来
private CountDownLatch comingTag;
// 等待裁判说准备开始
private CountDownLatch waitTag;
// 等待起泡
private CountDownLatch waitRunTag;
// 起跑
private CountDownLatch beginTag;
// 所有运动员到达终点
private CountDownLatch endTag;
public MyThread(CountDownLatch comingTag, CountDownLatch waitTag, CountDownLatch waitRunTag, CountDownLatch beginTag, CountDownLatch endTag) {
this.comingTag = comingTag;
this.waitTag = waitTag;
this.waitRunTag = waitRunTag;
this.beginTag = beginTag;
this.endTag = endTag;
}
@Override
public void run() {
try {
System.out.println("运动员使用不同交通工具不同速度到达起跑重点,正在向这边走");
Thread.sleep((int) Math.random() * 10000);
System.out.println(Thread.currentThread().getName() + " 到达起跑点...");
comingTag.countDown();
System.out.println("等待裁判说准备..");
waitTag.await();
System.out.println("各就各位,准备起跑..");
Thread.sleep((int) Math.random() * 10000);
waitRunTag.countDown();
beginTag.await();
System.out.println(Thread.currentThread().getName() + "运动员起跑,并且比赛过程用时不确定..");
Thread.sleep((int) Math.random() * 10000);
endTag.countDown();
System.out.println(Thread.currentThread().getName() + "运动员到达终点");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
CountDownLatch comingTag = new CountDownLatch(10);
CountDownLatch waitTag = new CountDownLatch(1);
CountDownLatch waitRunTag = new CountDownLatch(10);
CountDownLatch beginTag = new CountDownLatch(1);
CountDownLatch endTag = new CountDownLatch(10);
MyThread[] tArr = new MyThread[10];
for (int i = 0; i < 10; i++) {
tArr[i] = new MyThread(comingTag, waitTag, waitRunTag, beginTag, endTag);
tArr[i].start();
}
System.out.println("裁判员在等待选手到来");
comingTag.await();
System.out.println("裁判员看到所有运动员来了,各就各位前巡视,用时5秒");
Thread.sleep(5000);
waitTag.countDown();
System.out.println("各就各位...");
waitRunTag.await();
Thread.sleep(2000);
System.out.println("发令枪响");
beginTag.countDown();
endTag.await();
System.out.println("所有运动员到达,统计比赛名次");
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法await(long timeout,TimeUnit unit)
此方法的作用是使线程在指定的最大时间单位内进入WAITING状态,如果超过这个时间则自动唤醒,程序继续向下运行。
参数timeout是等待的时间,unit参数是时间的单位。
JDK API
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
- 由于调用 countDown() 方法,计数到达零;或者
- 其他某个线程中断当前线程;或者
- 已超出指定的等待时间。
如果计数到达零,则该方法返回 true 值。
public class MyService {
public CountDownLatch count = new CountDownLatch(1);
public void testMethod() {
try {
System.out.println(Thread.currentThread().getName() + "准备" + System.currentTimeMillis());
count.await(3, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() + "结束" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MyThread extends Thread {
private MyService service;
public MyThread(MyService service) {
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
public class Run {
public static void main(String[] args) {
MyService service = new MyService();
MyThread[] tArr = new MyThread[3];
for (int i = 0; i < tArr.length; i++) {
tArr[i] = new MyThread(service);
}
for (int i = 0; i < tArr.length; i++) {
tArr[i].start();
}
}
}
输出结果:
Thread-0准备1496366982943
Thread-2准备1496366982943
Thread-1准备1496366982943
Thread-1结束1496366985954
Thread-2结束1496366985954
Thread-0结束1496366985954
从代码中可以看出,没有调用countDown
()
方法,超时后,程序自动往下执行
方法getCount()的使用
该方法作用是:获取当前计数器的值。
public class Run {
public static void main(String[] args) {
CountDownLatch down = new CountDownLatch(3);
System.out.println("1-- " + down.getCount());
down.countDown();
System.out.println("2-- " + down.getCount());
down.countDown();
System.out.println("3-- " + down.getCount());
down.countDown();
System.out.println("4-- " + down.getCount());
down.countDown();
System.out.println("5-- " + down.getCount());
}
}
输出结果:
1-- 3
2-- 2
3-- 1
4-- 0
5-- 0
从结果看出,当计数器为
0
时,再次调用
countDown()
,计数器数量也不会再下降。