一、什么是CountDownLatch
CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他2个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
方法列表:
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
二、CountDownLatch数据结构
CountDownLatch通过共享锁实现。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。
三、源码分析
3.1 CountDownLatch(int count)
该方法主要就是创建一个Sync对象,Sync是一个内部类,继承于AQS:
创建Sync对象时,构造函数中的setState方法在AQS内部实现:
在AQS中,state是一个private volatile long类型的对象。
对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。
3.2 await()
该方法实际上是调用的AQS的acquireSharedInterruptibly(1);
acquireSharedInterruptibly()的作用是获取共享锁。如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。
tryAcquireShared()在CountDownLatch.java中被重写
tryAcquireShared()的作用是尝试获取共享锁。如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。
doAcquireSharedInterruptibly(需要补充)
3.3 countDown()
该函数实际上调用releaseShared(1)释放共享锁。
releaseShared在AQS中的实现:
releaseShared()的目的是让当前线程释放它所持有的共享锁。它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功(返回false),则直接返回;尝试失败(返回true,最后一个线程执行counrDown),则通过doReleaseShared()去释放共享锁,“主线程”就可以继续执行了。
tryReleaseShared()在CountDownLatch.java中被重写:
如上代码可以看到首先获取当前状态值(计数器值),如果当前状态值为 0 则直接返回 false ,则countDown()方法直接返回;否则执行代码使CAS设置计数器减一,CAS失败则循环重试,否则如果当前计数器为 0 则返回 true 。返回 true 后,说明当前线程是最后一个调用countDown()方法的线程,那么该线程除了让计数器减一外,还需要唤醒调用CountDownLatch的await 方法而被阻塞的线程。之所以使用第一个if进行判断, 是为了防止计数器值为 0 后,其他线程又调用了countDown方法,状态值就会变成负数。
四、CountDownLatch的使用示例
主线程等待两个子线程执行完毕后再执行:
package com.juc.countdownlatch;
import java.util.concurrent.CountDownLatch;
/**
* @Author: 98050
* @Time: 2018-12-20 20:50
* @Feature: CountDownLatch的使用
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+",子线程开始执行");
/**
* 计数器的值减一
*/
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+",子线程执行结束");
}
}).start();
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+",子线程开始执行");
/**
* 计数器的值减一
*/
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+",子线程执行结束");
}
}).start();
/**
* 计数器值为0,恢复任务继续执行
*/
countDownLatch.await();
System.out.println("两个子线程执行完毕,主线程继续执行");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
五、应用场景
1、其它的一些线程需要某个线程做准备工作。例如:数据库的连接等。
2、某个线程需要等待一些线程工作完之后清理资源。断开数据库的连接等。