CountDownLatch用在一个操作需要等待其他多个操作都执行完毕之后才会执行的场合,可以理解有一个计数器,某个动作需要等待计数器减到0的时候会发运行。用汽车等待乘客的例子好理解些,比如一辆小汽车,必须等待满3个乘客才会发车,我们来看看例子:
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Thread t = new Thread(()-> {
System.out.println("小汽车等待,满3个乘客发出");
try {
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("小汽车开车了");
});
t.start();
Thread.sleep(1000);
arrive(latch);
arrive(latch);
arrive(latch);
// 等待线程t结束
t.join();
System.out.println("end");
}
private static void arrive(CountDownLatch latch){
System.out.println("有1个乘客到达了");
latch.countDown();
}
以上代码运行结果如下,
小汽车等待,满3个乘客发出
有1个乘客到达了
有1个乘客到达了
有1个乘客到达了
小汽车开车了
end
从上面例子中我们可以看到,await()方法会阻塞线程,直到3次调用countDown()后,计数器为0的时候,线程才会被唤醒。下面我们就从源码来分析CountDownLatch是如何做到的。
一、基础代码结构
从下面代码看到,对CountDownLatch 的主要逻辑在Sync中,而Sync基础了AQS,也就是说CountDownLatch也有了同步队列的属性,可以阻塞线程。
public class CountDownLatch {
/**
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
// 状态为0就返回1,否则返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/** 这个方法会对计数器减一,当前计数器为0,或者减一后计数器为0,则返回true*/
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
/** 构造器中传入计数器大小,其实就是设置AQS的状态值,每次调用CountDown会减一 */
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
}
二、阻塞线程
阻塞线程的方法是await(),它会直接调用AQS的acquireSharedInterruptibly()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/** 下面代码在AQS类中 */
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果返回小于0,说明计数器没有减到0,此时调用doAcquireSharedInterruptibly(arg)方法阻塞线程,直到计数器为0
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
三、计数器减一
每调用一次ountDown()计数器就会减一,我们从代码中可以看到它是把AQS的状态减一
public void countDown() {
sync.releaseShared(1);
}
/** 以下代码在AQS类中*/
public final boolean releaseShared(int arg) {
// 判断计数器是否为0,为0的话调用doReleaseShared()唤醒阻塞的线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
四、总结
CountDownLatch是基于AQS实现的,具有阻塞线程的作用。在一些需要等待多个动作完成后才能继续执行下一个动作的场景中有很好用处,比如开篇说的汽车等乘客。
本文深入解析了CountDownLatch的工作原理,展示了如何使用CountDownLatch在Java中实现多线程的同步控制,通过具体示例解释了其在实际场景中的应用。
1083

被折叠的 条评论
为什么被折叠?



