CountDownLatch分析
基于AQS的一个只能递减的计数器,且无法复用 ,所以一般放在业务方法里
简介
使用方式:可以在构造方法中设置任务数量,并行执行任务时,子线程执行完任务后将计数减一,等到计数为0时,代表所有的任务执行完毕,唤醒主线程执行后续的操作
基于AQS实现,主线程阻塞在AQS队列中,等待计数为0时唤醒主线程进行后续的业务
内部方法很少:
提示:以下是本篇文章正文内容,下面案例仅供参考
一、基本使用
public class CountDownLatchTest {
// 创建一个线程池来执行任务
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3,
3,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
// 一般不用于类变量,因为无法复用,这里只是测试,一般放在业务里
private static CountDownLatch countDownLatch = new CountDownLatch(3);
public static void task1() {
System.out.println("执行任务1");
try {
// 执行1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务1执行结束");
countDownLatch.countDown();
}
public static void task2() {
System.out.println("执行任务2");
try {
// 执行1.5s
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务2执行结束");
countDownLatch.countDown();
}
public static void task3() {
System.out.println("执行任务3");
try {
// 执行2s
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务3执行结束");
countDownLatch.countDown();
}
public static void main(String[] args) throws InterruptedException {
// 提交任务
threadPoolExecutor.submit(CountDownLatchTest::task1);
threadPoolExecutor.submit(CountDownLatchTest::task2);
threadPoolExecutor.submit(CountDownLatchTest::task3);
// 等待并行任务结束
// countDownLatch.await();
// 如果10s任务还没有处理完
boolean await = countDownLatch.await(10000, TimeUnit.MINUTES);
// 时间内没有处理完
if (!await) {
System.out.println("任务执行未结束,主线程执行额外的逻辑");
} else {
// 规定时间内任务处理完了
System.out.println("并行任务执行结束,主线程执行后续逻辑");
}
}
}
二、原理分析
内部的计数器就是AQS的state
1.有参构造方法
代码如下(示例):
public CountDownLatch(int count) {
// 0虽然可以设置,但无意义
if (count < 0) throw new IllegalArgumentException("count < 0");
// 给state变量赋值
this.sync = new Sync(count);
}
2.await方法分析
代码如下(示例):
// 阻塞主线程
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// AQS实现
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 线程被设置中断时,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取资源,获取成功返回1,不进入阻塞逻辑中
if (tryAcquireShared(arg) < 0)
// 未获取到资源,将主线程阻塞在AQS同步队列中(有子线程在执行任务)
doAcquireSharedInterruptibly(arg);
}
// countDownLatch实现
protected int tryAcquireShared(int acquires) {
// state如果为0说明资源没有被占用,返回1
return (getState() == 0) ? 1 : -1;
}
// AQS实现,主线程入队阻塞
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
// 将线程封装成node,追加到双向链表(同步队列)尾部
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
// 死循环,一直等子任务执行完,释放掉锁后,主线程抢占到锁为止
for (;;) {
// 获取pre节点
final Node p = node.predecessor();
// head是伪节点,内部没有线程
if (p == head) {
// 再尝试拿一次资源
int r = tryAcquireShared(arg);
// 拿到资源是唯一的出口
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 判断主线程是否允许挂起在队列中(WAITING)
if (shouldParkAfterFailedAcquire(p, node) &&
// 执行LockSupport.park()线程挂起,如果线程被设置了中断标志位,抛出InterruptedException异常
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.分析countDown方法
将state减一,减成0时去AQS队列中唤醒主线程
public void countDown() {
// 释放一次资源
sync.releaseShared(1);
}
// AQS实现
public final boolean releaseShared(int arg) {
// 如果资源被完全释放,进入if逻辑
if (tryReleaseShared(arg)) {
// 唤醒AQS同步队列中阻塞的主线程
doReleaseShared();
return true;
}
return false;
}
// countDownLatch实现
protected boolean tryReleaseShared(int releases) {
// 死循环
for (;;) {
int c = getState();
// 锁已经被释放完了
if (c == 0)
return false;
int nextc = c-1;
// CAS修改state
if (compareAndSetState(c, nextc))
// 如果完全释放,返回true
return nextc == 0;
}
}
// AQS的原有逻辑
private void doReleaseShared() {
// 死循环
for (;;) {
// 获取head
Node h = head;
// 如果head不为null && head不为tail,说明链表(AQS同步队列)里有值
if (h != null && h != tail) {
// 如果head的节点状态为-1
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
// 将head节点状态修改为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
// 修改失败再来一次
continue; // loop to recheck cases
// 修改成功,唤醒head的下一节点,如果next有问题,继续向下寻找可以唤醒的节点
unparkSuccessor(h);
}
// head节点状态不为-1
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
总结
使用AQS中内置state作为资源数,并发执行任务时,每个线程执行完任务后都执行countDown()方法递减资源数,减成0时会执行doReleaseShared方法去AQS同步队列中唤醒被阻塞的主线程
主线程也可以不等待子线程执行任务完毕,使用带有阻塞时间的await方法,时间到后自动唤醒,返回值是布尔,可以代表任务是否处理完了
countDownLatch使用完后,无法再次使用,需要重新new