一、概述
类 CountDownLatch 是一个同步功能的辅助类,使用效果是给定一个计数,当使用这个CountDownLatch类的线程判断计数不为0时,则呈wait状态,如果为0时则继续运行。实现等待与继续运行的效果分别需要使用await()和countDown()方法来进行。调用await()方法时判断计数是否为0,如果不为0则呈等待状态。其他线程可以调用countDown()方法将计数减1,当计数减到为0时,呈等待的线程继续运行。而方法getCount()就是获得当前的计数个数。
二、使用场景
1、某一线程在开始运行前等待n个线程执行完毕
比如实现一个机票比价场景,需要调用各个航空公司的机票价格,然后进行价格排序,就可以通过 CountDownLatch 来实现,代码如下所示:
public class CountDownLatchExample1 {
public static void main(String[] args) {
List<String> airs = Arrays.asList("东方航空", "南方航空", "成都航空", "北京航空");
//定义一个CountDownLatch对象,指定数量为航空公司的个数
CountDownLatch latch = new CountDownLatch(airs.size());
List<Pair<String, Integer>> list = new ArrayList<>();
//定义四个线程模拟调用 航空公司的外部接口
Thread[] threads = new Thread[airs.size()];
for(int i = 0; i < airs.size(); i++){
threads[i] = new Thread(() -> {
try {
// 模拟调用耗时
TimeUnit.SECONDS.sleep(current().nextInt(10));
//构建价格
Pair<String, Integer> pair = new Pair<>(Thread.currentThread().getName(), current().nextInt(100));
list.add(pair);
System.out.println(pair.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
latch.countDown(); //执行完后 是计数器减1
}
}, airs.get(i));
}
//启动所有的线程
Stream.of(threads).forEach(Thread::start);
System.out.println("等待所有的线程执行完");
try {
latch.await(); // 在此等待
} catch (InterruptedException e) {
e.printStackTrace();
}
list.sort((o1, o2) -> {
if(o1.getValue() > o2.getValue()){
return 1;
}else if (o1.getValue().equals(o2.getValue())){
return 0;
}else {
return -1;
}
});
System.out.println("========最终结果=======");
list.forEach(System.out::println);
}
}
执行结果:
2、实现多个线程开始执行任务的最大并行性
public class CountDownLatchExample2 {
//裁判类
static class Referee{
private CountDownLatch downLatch = new CountDownLatch(1);
private CountDownLatch latch;
Referee(int count){
latch = new CountDownLatch(count);
}
// 各就位
void prepare() {
try {
System.out.printf("线程[%s]准备就绪 \n", Thread.currentThread().getName());
latch.countDown(); //运动员就位
downLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("线程[%s]结束 \n", Thread.currentThread().getName());
}
//开始执行
public void start(){
try {
latch.await(); //等待运动员就位
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始");
downLatch.countDown();
}
}
// 运动员类
static class SportsMan extends Thread{
private Referee referee;
SportsMan(Referee referee) {
this.referee = referee;
}
@Override
public void run() {
referee.prepare(); // 运动员准备
}
}
public static void main(String[] args) {
//定义裁判指定10个运动员
Referee referee = new Referee(10);
//定义 10 个运动员
SportsMan[] mans = new SportsMan[10];
for (int i = 0; i < 10; i++){
mans[i] = new SportsMan(referee);
mans[i].setName("Thread-" + i);
}
Stream.of(mans).forEach(Thread::start);
referee.start();
}
}
执行结果:
三、源码解析
1、类结构
2、Sync 一个静态内部类
//构造方法 CountDownLatch 的构造方法最终调用的是 Sync的构造。
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count); //初始化计数器
}
// 获取当前计数器
int getCount() {
return getState();
}
// 试图在共享模式下获取对象状态
// 申请资源 如果 数量为 0,则不进行阻塞, 否则进入阻塞
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 试图设置状态来反映共享模式下的一个释放
// 减少计数器数量,直到数量为0,
protected boolean tryReleaseShared(int releases) {
// 死循环保证最终这个状态值能设置成功
for (;;) {
// 获取当前状态值,CountDownLatch中的锁存器计数
int c = getState();
// 如果状态为0,表示CountDownLatch中的锁存器计数为0,就直接返回
if (c == 0)
return false;
// 如果状态不为0,则将状态减一
int nextc = c-1;
/*
* 更新state值,即锁存器计数值,通过CAS保证线程安全
* CAS操作有3个操作数,内存值M,预期值E,新值U,如果M==E,则将内存值修改为B,否则啥都不做
* compareAndSetState(c, nextc)方法:
* c:表示预期值
* nextc:要更新的值
* 在此处,表示c==getState()时返回true,并更新state值为nextc
* 如果c!=getState()时,表示已经有其他线程更新了state值,
* 所以这里不进行更新,直接返回false,通过死循环再重新获取最新的getState()值
*/
if (compareAndSetState(c, nextc))
// 返回状态是否为0判断,为0表示锁存器计数为0,可以唤醒await的进程了
// 这里唤醒进程的操作也是通过AQS进行实现的
return nextc == 0;
}
}
}
从源码可知 CountDownLatch 内部是通过 AQS 的共享模式实现的
3、await() 和 await(long timeout, TimeUnit unit) 方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
此函数将会使当前线程在锁存器倒计数至零之前一直等待,除非超时或者被中断。
4、countDown() 方法
此函数将递减锁存器的计数,如果计数到达零,则释放所有等待的线程
/**
* count值减 1,直到计数达到零,释放所有等待的线程 。
*
* <p>如果当前计数大于零,则递减。
* 如果新计数为零,则重新启用所有等待的线程 ,达到线程调度的目的。
*
* <p>如果当前计数等于零,则没有任何反应。
*/
public void countDown() {
sync.releaseShared(1);
}
注意:
- 初始化CountDownLatch 时的计数器必须大于0,只有当计数器等于 0 的时候,调用 await() 方法是不会阻塞。
- 任务的结束并不一定代表着正常的结束,有可能是在运算的过程中出现错误,因此为了能够正确地执行countDown(),需要将该方法的调用放在finally代码块中,否则就会出现主线程(任务)await()方法永远不会退出阻塞的问题。