CountDownLatch是Java并发编程中的一种同步工具,它允许一个或多个线程等待其他线程完成操作后再继续执行。它是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在阻塞等待的线程就可以恢复执行任务。
用一个例子来看下这个用法:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount);
// 创建并启动多个工作线程
for (int i = 0; i < workerCount; i++) {
WorkerThread worker = new WorkerThread("Worker " + (i + 1), latch);
worker.start();
}
// 主线程等待所有工作线程完成
System.out.println("主线程等待工作线程完成...");
latch.await();
// 所有工作线程完成后,主线程继续执行
System.out.println("所有工作线程已完成,主线程继续执行");
}
}
class WorkerThread extends Thread {
private String name;
private CountDownLatch latch;
public WorkerThread(String name, CountDownLatch latch) {
this.name = name;
this.latch = latch;
}
@Override
public void run() {
System.out.println(name + " 开始执行");
// 模拟工作线程执行任务的耗时
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + " 执行完成");
// 工作线程完成任务,计数器减一
latch.countDown();
}
}
在上述代码中,首先创建了一个CountDownLatch对象,并设置初始计数器的值为workerCount,这里假设有3个工作线程。
然后,使用一个循环创建并启动了多个WorkerThread工作线程,每个线程执行一些模拟任务。
主线程调用latch.await()方法,使主线程进入等待状态,直到计数器归零。这意味着主线程将等待所有工作线程执行完成。
每个工作线程在执行任务之前,会输出一条开始执行的信息,并模拟任务的耗时。
任务执行完毕后,工作线程调用latch.countDown()方法,将计数器减一。当所有工作线程都完成任务时,计数器归零,主线程解除阻塞,继续执行后续操作。
从代码层面看一下这种机制实现的原理,首先看一下它的构造方法
CountDownLatch latch = new CountDownLatch(workerCount);
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
看到this.sync是不是有点熟悉,我在另外一篇信号量:控制并发访问与资源分配中提到过。
Sync(int count) {
setState(count);
}
protected final void setState(int newState) {
state = newState;
}
同样的,利用AQS中的state来维护这个计数器。
接着看当工作线程执行完任务后,会将计数器减1。
latch.countDown();
public void countDown() {
sync.releaseShared(1);
}
继续点进去,看实现:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
这段代码已经与Semaphore的release方法执行的是同一段方法入口,只是对应着这几种工具类的不同实现。
在这里我们只需要看CountDownLatch的实现即可。
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
看代码能知道大概干了几件事:
- 首先,获取当前计数器的值,然后判断计数器的值是否为0。如果为0说明已经没有等待的线程,不需要再执行后面的操作,直接返回false。如果不为0,继续执行,定义一个变量用于接收下一个计数器的值。
- 然后通过CAS的方式更新计数器的值。
compareAndSetState方法是一个原子操作,用于比较当前值和期望值,如果相等则更新为新值,如果不相等则不更新。如果更新成功,说明成功递减计数器的值。 - 最后,检查递减后的计数器值是否为0。如果为0,表示所有等待的线程都已被释放,可以唤醒所有等待的线程继续执行,此时返回true。如果不为0,表示还有等待的线程,继续等待其他线程释放资源,返回false。
主线程的await():
latch.await();
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
和Semaphore中acquire方法的逻辑相同,最终都调了AQS的acquireSharedInterruptibly方法。
总结:
使用CountDownLatch的一般步骤如下:
-
创建一个CountDownLatch对象,指定初始计数器的值。
-
在主线程中调用
await()方法,使主线程等待计数器归零。 -
在其他线程中执行任务,任务完成后调用
countDown()方法,递减计数器的值。 -
当计数器变为零时,主线程解除阻塞,继续执行后续操作。
CountDownLatch的典型应用场景:
-
主线程等待多个工作线程完成后再继续执行。
-
并发测试中,主线程等待所有测试线程执行完毕后进行结果统计。
-
多个线程协同工作,某个线程等待其他线程的信号后再执行特定操作。
总之,CountDownLatch是一种用于线程间协调和同步的工具,它提供了一种简单且灵活的方式,让线程能够等待其他线程的完成。通过合理使用CountDownLatch,可以避免线程之间的竞争条件和数据不一致性问题,实现更加稳定和可靠的并发编程。