本文参考:https://mp.weixin.qq.com/s/glMjpmDHmQhcQCXYO0Faqw
倒计时CountDownLatch
CountDownLatch
CountDownLatch是一个多线程控制类,称之为“倒计时器”,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。
模拟收集七龙珠
等到集齐7颗龙珠就能召唤神龙
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class MyCountDown{
private static int maxCollectNum=7;
//创建倒计时器,等待线程为7个
private static CountDownLatch countDownLatch=new CountDownLatch(maxCollectNum);
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= maxCollectNum; i++) {
int index=i;
new Thread(()->{
try {
System.out.println("第"+index+"颗已收集");
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//每收集到一课,需要等待的颗数减1
countDownLatch.countDown();
}).start();
}
//等待检查,上述7个线程执行完毕后,执行await后面的代码
countDownLatch.await();
System.out.println("集齐7颗龙珠,召唤神龙");
}
}
可以体会到CountdownLatch的用途,已经执行的线程需要等待未完成的线程,直到已完成线程个数达到构造器参数的数量,然后执行后面的程序。
特性
- 构造函数
CountDownLatch countDownLatch=new CountDownLatch(maxCollectNum);
maxCollectNum表示需要等待执行完毕的线程数量; - 在每一个线程执行完后都要执行
countDownLatch.countDown();
用来计数; - 只有所有线程执行完毕后,才会执行
countDownLatch.await();
后的代码; - 可以看出上面代码中阻塞的是主线程;
作用
CountDownLatch是在1.5之后被引入的,位于java.util,concurrent包下,这个类能够使一个线程等待其他线程完成各自的工作后在执行。
执行流程
- 构造器中的计数值实际上就是闭锁需要等待的线程数量,这个只能被设置一次,而且没有提供任何机制起重新设置这个值;
- 与CountdownLatch第一次交互是主线程等待其他线程,必须启动其他线程后调用await方法,这样主线程就会在这个方法上阻塞,直到其他线程完成;
- 其他线程必须引用闭锁对象,用来通知对象已经完成任务,这种机制通过
countDownLatch.countDown();
来完成。
使用场景
- 实现最大并行性;
- 开始执行前等待n个线程完成各自任务
循环屏障CycliBarrier
和CountdownLatch类似,但是功能更强大,字面意思是可循环使用的屏障,而CountdownLatch可以认为只有一个屏障。
继续七龙珠问题,需要先召集七个法师在同一个地点,等人齐了之后(第一个屏障),在分别去不同的地方寻找七龙珠,等到七个法师都找到七龙珠回来后(第二个屏障),在召唤神龙。
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
public class MyCountDown{
private static int maxCollectNum=7;
public static void main(String[] args) throws InterruptedException {
//设置第一个屏障,等待集齐7位法师
CyclicBarrier cyclicBarrier = new CyclicBarrier(maxCollectNum, new Runnable() {
@Override
public void run() {
System.out.println("7个法师召集完毕,去往不同地方寻找龙珠");
sumonDragon();
}
});
for (int i = 1; i <= maxCollectNum; i++) {
int index=i;
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"召唤第"+index+"个法师");
//创建的子线程在此会被阻塞,直到线程数量达到指定的7个,然后执行run中的程序
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
private static void sumonDragon() {
//设置第二个平屏障,等待七位法师收集完7颗龙珠,召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(maxCollectNum, new Runnable() {
@Override
public void run() {
System.out.println("集齐七颗龙珠,召唤神龙");
}
});
for (int i = 0; i < maxCollectNum; i++) {
int index=i;
new Thread(()->{
System.out.println("第"+index+"龙珠已被找到");
try {
//创建的子线程在此会被阻塞,直到线程数量达到指定的7个,然后执行run中的程序
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
使用CyclicBarrier.reset()
可知,可以使CyclicBarrier回到最初始状态;
CylicBarrier和CountDownLatch的区别
- CountDownLatch计数器只能使用一次,而CylicBarrier的计数器可以使用reset方法重置。
- CyclicBarrier还有其他有用的方法,如getNumberWaiting获得阻塞线程的数量;
- CountdownLatch会阻塞主线程,CyclicBarrier不会阻塞主线程,只会阻塞子线程;