首页
学习
活动
专区
圈层
工具
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java多线程并发编程CountDownLatch源码解读和简单应用

在多线程并发编程中,如果线程的执行有前置条件。比如做一个任务,需要执行A、B、C、D四个步骤,A和B可以同时进行,但是C的执行需要等到A和B执行结束,D要等到C执行结束。这种情况你会怎么做?

在这种情况下,CountDownLatch就可以发挥它的作用了。

一、CountDownLatch简介

它允许一个线程等待多个线程执行完毕后再继续执行。

通俗来说,它就像一个计数器,主线程会等待直到计数器的值减到零,所有的工作线程执行完毕。

工作原理:

CountDownLatch 初始化时,计数器的值是一个正整数,表示需要等待的事件数量。

每当一个线程完成它的任务后,会调用 countDown() 方法来将计数器减一。

其他线程可以调用 await() 方法等待计数器变为零,直到计数器值为零时,所有等待的线程才会继续执行。

二、CountDownLatch 1.8 源码解读

1. 构造函数

public class CountDownLatch implements java.io.Serializable {

private final Sync sync;

public CountDownLatch(int count) {

if (count < 0) throw new IllegalArgumentException("count < 0");

this.sync = new Sync(count);

}

// 内部 Sync 类(用于处理计数器同步的核心实现)

static final class Sync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = 1L;

Sync (int count) {

setState(count); // 设置初始计数器值

}

protected int tryAcquireShared(int acquires) {

return (getState() == 0) ? 1 : -1; // 计数器为0时,表示可以获取锁

}

protected boolean tryReleaseShared(int releases) {

for (;;) {

int current = getState();

if (current == 0) {

return false;

}

int next = current - 1;

if (compareAndSetState(current, next)) {

return next == 0;

}

}

}

}

}

CountDownLatch(int count) 用于初始化计数器 count,并创建一个 Sync 对象作为同步工具。计数器的初始值就是我们传递给构造函数的参数。

内部类 Sync 继承了 AbstractQueuedSynchronizer(AQS)。

AQS 是一个用于构建同步工具类的框架,它通过管理锁的状态(如计数器值)来实现并发控制。

Sync 通过重写 AQS 的 tryAcquireShared 和 tryReleaseShared 方法来实现计数器的获取和释放。

2. await() 方法:让当前线程等待

await() 方法会让当前线程阻塞,直到计数器的值变为 0。

public void await() throws InterruptedException {

sync.acquireSharedInterruptibly(1);

}

功能:调用 await() 方法后,当前线程会被阻塞,直到计数器减到零或者被中断。acquireSharedInterruptibly(1) 会尝试以共享模式获取锁,并且支持响应中断。

底层实现:通过 AbstractQueuedSynchronizer 提供的 acquireSharedInterruptibly() 方法实现,线程被加入到阻塞队列中,直到计数器为零或者发生中断。

3. countDown() 方法:减少计数器

countDown() 方法会让计数器减一,当计数器减到 0 时,唤醒所有等待的线程。

public void countDown() {

sync.releaseShared(1);

}

功能:每次调用 countDown(),计数器减一。如果计数器减为零,会唤醒所有在 await() 中等待的线程。

底层实现:通过 AbstractQueuedSynchronizer 提供的 releaseShared() 方法实现,当 countDown() 被调用时,调用 tryReleaseShared() 减少计数器,并判断是否需要唤醒等待线程。

4. getCount() 方法:获取当前计数器的值

public long getCount() {

return sync.getState();

}

功能:返回当前计数器的值,用于检查还有多少个线程需要等待。

三、应用示例

模拟多个线程并行执行任务,假设我们有多个线程,每个线程执行一些任务,主线程会等待这些任务完成之后才继续执行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

public static void main(String[] args) throws InterruptedException {

// 创建一个 CountDownLatch,计数器为 3,表示需要等待 3 个线程完成任务

CountDownLatch latch = new CountDownLatch(3);

// 任务线程

Runnable task = () -> {

try {

// 模拟任务处理,假设每个任务处理需要 1 秒

Thread.sleep(1000);

System.out.println(Thread.currentThread().getName() + " finished.");

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

} finally {

latch.countDown(); // 每个线程完成任务后,调用 countDown()

}

};

// 启动 3 个线程来执行任务

for (int i = 0; i < 3; i++) {

new Thread(task).start();

}

// 主线程等待,直到计数器为 0,表示所有线程都已完成

latch.await(); // 等待所有任务完成

System.out.println("All threads finished. Main thread resumes.");

}

}

创建了一个 CountDownLatch 对象,计数器初始化为 3,表示主线程需要等待 3 个线程完成任务。

启动了 3 个线程,每个线程执行一个任务(模拟为 1 秒的睡眠),并在任务完成后调用 countDown() 来减少计数器。

主线程调用 await(),等待计数器值为 0 后才继续执行,最后输出“主线程恢复”。

运行结果:

Thread-0 finished.

Thread-1 finished.

Thread-2 finished.

All threads finished. Main thread resumes.

从上述示例中,我们看到,CountDownLatch 通过计数器来同步多个线程,主线程会等待计数器为 0 时才继续执行。

await():让当前线程阻塞,直到计数器为 0。

countDown():减少计数器的值。

四、CountDownLatch 的优缺点

CountDownLatch 是一个非常有用的并发控制工具,但它也有一些局限性。

优点:

简单易用:

CountDownLatch 提供了简单的 API,使用 await() 和 countDown() 方法即可实现线程间的同步,容易理解和使用。

线程同步:

它允许多个线程执行完任务后,主线程或其他线程才继续执行。比如在并发任务的最后阶段需要等待所有线程完成,可以非常方便地进行控制。

高效的线程等待:

CountDownLatch 使用的是基于 AbstractQueuedSynchronizer(AQS)的高效同步机制,线程通过 acquireShared() 和 releaseShared() 方法协调,避免了频繁的轮询,效率较高。

适用于一次性同步:

CountDownLatch 适用于需要一次性等待多个线程完成的场景。一旦计数器为 0,所有等待的线程会被唤醒,并且计数器无法重置,因此非常适合于这种单次等待。

缺点:

不能重用:

CountDownLatch 设计为只能使用一次。一旦计数器减到 0,CountDownLatch 就不再有效,无法重新启动计数器。因此,如果需要多次等待线程完成,CountDownLatch 不是合适的选择。对于这种情况,可以考虑使用 CyclicBarrier 或其他同步工具。

主线程阻塞:

如果主线程调用了 await(),它会被阻塞直到计数器减为 0。如果某些工作线程的任务执行时间较长,主线程可能会长时间阻塞,造成程序响应延迟或效率降低。

不支持中断后重试:

CountDownLatch 在 await() 阻塞时,如果线程被中断,它会抛出 InterruptedException,此时不能恢复继续等待。如果线程中断后需要重新尝试等待,CountDownLatch 不支持这种行为。这个问题在需要重试机制时需要额外的处理。

无法控制特定线程的等待:

CountDownLatch 是全局的,它让所有调用 await() 的线程一起阻塞并等待。若需要更细粒度的控制,如让某些线程等待其他特定线程完成任务,CountDownLatch 并不适合,可能需要使用 Semaphore 或其他工具。

五、最后总结

CountDownLatch 是一种简单而高效的工具,适用于许多并发编程场景。它的设计理念就是让一个或多个线程等待一组线程的执行完成,通常用于初始化或并行任务结束的同步。不过,它也有一些局限性,比如不能重用、阻塞主线程等。根据不同的需求,我们可以选择最合适的工具来解决问题。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OkCLkC6whpF-VN9iOSfW5Dqw0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券