工具类-计数器 CountDownLatch
也有叫倒计时门闩、倒计时锁、计数器,作用可以 让一个或多个线程阻塞,直到其他线程完成再继续执行
一次性使用,计数器只能递减,不能重置,不能增加
主要方法
// 构造函数(初始化计数器)
public CountDownLatch(int count)
// 等待直到计数器归零(可中断)
public void await()
// 带超时的等待
public boolean await(long timeout, TimeUnit unit)
// 递减计数器(计数-1)
public void countDown()
// 获取当前计数值
public long getCount()
使用示例
基础示例
// 创建计数器(3个子任务)
CountDownLatch latch = new CountDownLatch(3);
// 启动工作线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 模拟任务执行
Thread.sleep((long)(Math.random() * 2000));
System.out.println(Thread.currentThread().getName() + " 完成任务");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown(); // 任务完成,计数器-1
}
}).start();
}
// 主线程等待所有任务完成
latch.await();
System.out.println("所有任务已完成,继续主流程");
带超时示例
ExecutorService executor = Executors.newFixedThreadPool(5);
CountDownLatch latch = new CountDownLatch(10);
// 提交10个任务
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
try {
// 执行任务...
} finally {
latch.countDown();
}
});
}
// 等待所有任务完成(最多等5秒)
if (latch.await(5, TimeUnit.SECONDS)) {
System.out.println("全部任务按时完成");
} else {
System.out.println("部分任务超时未完成");
}
executor.shutdown();
多个线程等待
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(3); // 需要等待3个前置任务
// 工作线程
for (int i = 0; i < 3; i++) {
int finalI = i+1;
pool.execute(() -> {
try {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(finalI));
System.out.println("工作线程"+finalI+"完成");
} finally {
latch.countDown(); // 每个任务完成时计数器-1
}
});
}
// 等待线程
for (int i = 0; i < 2; i++) {
int finalI = i+1;
pool.execute(() -> {
try {
System.out.println("等待线程"+finalI+"等待...");
latch.await(); // 等待前置任务完成
System.out.println("等待线程"+finalI+"完成");
// 执行后续任务...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
pool.shutdown();
初始化
// java.util.concurrent.CountDownLatch#CountDownLatch
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// java.util.concurrent.CountDownLatch.Sync#Sync
Sync(int count) {
setState(count);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
protected final void setState(int newState) { // 底层就是初始时给 AQS 的 state 赋值
state = newState;
}
await() 原理
字面意思等待,底层原理也是获取锁,获取不到就入队并阻塞。只是这里的获取锁就是看 state 是否是 0
// java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) throw new InterruptedException();
if (tryAcquireShared(arg) < 0) // 获取锁,CountDownLatch 的处理是:返回 1(获取成功),返回 -1(获取失败)
doAcquireSharedInterruptibly(arg); // 获取锁失败的处理,线程入队,挂起线程
}
// java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
protected int tryAcquireShared(int acquires) { // 获取锁的做法就是判断 state 是否等于 0,等于0表示获取锁成功,不然返回-1表示获取失败
return (getState() == 0) ? 1 : -1;
}
doAcquireSharedInterruptibly() 方法用于处理获取锁失败的处理,也就是线程入队、挂起线程,是 AQS 实现的,这里就不展开了
Semaphore 里有介绍过这个方法,AQS 中介绍过 doAcquireShared() 方法,两个方法差不多,区别就是 doAcquireSharedInterruptibly() 能响应中断处理
countDown() 原理
字面意思计数-1,底层原理也是释放锁,就是把 state -1
// java.util.concurrent.CountDownLatch#countDown
public void countDown() {
sync.releaseShared(1);
}
// java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { // 释放锁
doReleaseShared(); // 释放成功的处理
return true;
}
return false;
}
// java.util.concurrent.CountDownLatch.Sync#tryReleaseShared
protected boolean tryReleaseShared(int releases) { // 释放锁就是把 state--,如果 state = 0 就表示释放成功,反之释放失败
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
doReleaseShared() 方法用于释放锁成功后的处理,就是处理线程出队和唤醒,因为是共享锁,所以会唤醒多个线程
这是有共性的方法,所以 AQS 已经实现好了,这里不赘述,想详细了解可以查看 AQS 从入门到精通
总结
- 基于 AQS 实现,当多个线程作为等待线程时,也支持公平和非公平
- 初始化时维护 state 的值
- 等待线程调用
await()方法时,如果 state 不等于 0 就进入队列阻塞( state!=0 表示获取锁失败) - 工作线程调用
countDown()方法时,让 state--- 如果减后 state = 0 表示释放锁成功,唤醒等待线程(共享锁,所以可能唤醒多个)
- 如果减后 state != 0 表示释放锁失败(不唤醒线程)

浙公网安备 33010602011771号