CountDownLatch 原理
CountDownLatch是同步工具类之一,可以指定一个计数值,在并发环境下由线程进行减1操作,当计数值变为0之后,被await方法阻塞的线程将会唤醒,实现线程间的同步。
public void startTestCountDownLatch() {
//定义一个数值为10的对象
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < threadNum; i++) {
final int finalI = i + 1;
new Thread(() -> {
System.out.println("thread " + finalI + " start");
Random random = new Random();
try {
Thread.sleep(random.nextInt(10000) + 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread " + finalI + " finish");
//计数器会减一
// 这个方法的作用是唤醒AQS的同步队列中,正在等待的第一个线程
// 而我们分析acquireSharedInterruptibly方法时已经说过,
// 若一个线程被唤醒,检测到count == 0,会继续唤醒下一个等待的线程
// 也就是说,这个方法的作用是,在count == 0时,唤醒所有等待的线程
countDownLatch.countDown();
}).start();
}
try {
//一直等待计数器减到0,程序继续向下执行
//方法的作用是尝试获取共享锁(count==0),若获取失败,则线程将会被加入到AQS的同步队列中等待
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadNum + " thread finish");
}
主线程启动10个子线程后阻塞在await方法,需要等所有子线程都执行完毕,主线程才能唤醒继续执行。
CountDownLatch内部定义了一个内部类Sync,继承自AQS,通过这个内部类来实现线程阻塞
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
/** 构造方法,接收count值,只有count减小为0时,线程才不会被await方法阻塞 */
Sync(int count) {
// CountDownLatch利用AQS的方式就是直接让count作为AQS的同步变量state
// 所以直接用state记录count值
setState(count);
}
/** 获取当前的count值 */
int getCount() {
return getState();
}
/**
* 这是AQS的模板方法acquireShared、acquireSharedInterruptibly等方法内部将会调用的方法,
* 由子类实现,这个方法的作用是尝试获取一次共享锁,对于AQS来说,
* 此方法返回值大于等于0,表示获取共享锁成功,反之则获取共享锁失败,
* 而在这里,实际上就是判断count是否等于0,线程能否向下运行
*/
protected int tryAcquireShared(int acquires) {
// 此处判断state的值是否为0,也就是判断count是否为0,
// 若count为0,返回1,表示获取锁成功,此时线程将不会阻塞,正常运行
// 若count不为0,则返回-1,表示获取锁失败,线程将会被阻塞
// 从这里我们已经可以看出CountDownLatch的实现方式了
return (getState() == 0) ? 1 : -1;
}
/**
* 此方法的作用是用来是否AQS的共享锁,返回true表示释放成功,反之则失败
* 此方法将会在AQS的模板方法releaseShared中被调用,
* 在CountDownLatch中,这个方法用来减小count值
*/
protected boolean tryReleaseShared(int releases) {
// 使用死循环不断尝试释放锁
for (;;) {
// 首先获取当前state的值,也就是count值
int c = getState();
// 若count值已经等于0,则不能继续减小了,于是直接返回false
// 为什么返回的是false,因为等于0表示之前等待的那些线程已经被唤醒了,
// 若返回true,AQS会尝试唤醒线程,若返回false,则直接结束,所以
// 在没有线程等待的情况下,返回false直接结束是正确的
if (c == 0)
return false;
// 若count不等于0,则将其-1
int nextc = c-1;
// compareAndSetState的作用是将count值从c,修改为新的nextc
// 此方法基于CAS实现,保证了操作的原子性
if (compareAndSetState(c, nextc))
// 若nextc == 0,则返回的是true,表示已经没有锁了,线程可以运行了,
// 若nextc > 0,则表示线程还需要继续阻塞,此处将返回false
return nextc == 0;
}
}
}
内部类Sync的实现非常简单,它只实现了AQS中的两个方法,即tryAcquireShared以及tryReleaseShared,这两个方法是AQS提供的使用共享锁的接口。
CountDownLatch实际上是一种共享锁机制,即锁可以同时被多个线程获取,这个不难理解,因为一旦count被减小为0,则所有线程通过await方法时,都能够顺利通过,不会因为获取不到锁而阻塞。而且从上面的实现中我们可以看到,Sync直接将count值作为AQS的state的值,只有state的值为0,线程才能获取锁,也就是获得执行权限。