CountDownLatch 并发编程中的同步利器
CountDownLatch 并发编程中的同步利器
文章目录
- CountDownLatch 并发编程中的同步利器
- 一、`CountDownLatch` 基础概念
- 1.1 什么是 `CountDownLatch`?
- 1.2 `CountDownLatch` 的核心方法
- 1.3 基本使用示例
- 二、`CountDownLatch` 实战应用
- 2.1 应用场景一:并行任务协调
- 2.2 应用场景二:多阶段并发处理
- 2.3 应用场景三:并发测试
- 三、源码解析
- 3.1 `CountDownLatch` 的构造与字段
- 3.2 核心方法实现
- 3.2.1 await 方法
- 3.2.2 countDown 方法
- 3.3 工作流程图解
- 四、`CountDownLatch` 的高级用法
- 4.1 带超时的等待
- 4.2 结合 CyclicBarrier 实现复杂同步
- 4.3 注意事项与最佳实践
- 五、`CountDownLatch` 与其他同步工具的对比
- 5.1 `CountDownLatch` vs CyclicBarrier
- 5.2 `CountDownLatch` vs Semaphore
- 5.3 `CountDownLatch` vs Join
- 六、面试中的 `CountDownLatch` 问题解析
- 6.1 基础概念题
- 6.2 代码实现题
- 6.3 原理分析题

在多线程并发编程中,线程同步是一个永恒的话题。当我们需要等待多个线程完成某些操作后再继续执行,或者需要等待某个条件满足后才能开始执行,CountDownLatch
便成为了一把利器。它就像一个倒计时器,只有当计数归零时,等待的线程才能继续执行。
一、CountDownLatch
基础概念
1.1 什么是 CountDownLatch
?
CountDownLatch
是 Java 并发包(java.util.concurrent)中的一个同步工具类,它允许一个或多个线程等待一系列指定操作的完成。CountDownLatch
的核心思想是维护一个计数器,每完成一个操作,计数器减一,当计数器归零时,等待的线程被释放继续执行。
这听起来有点抽象,我们可以通过一个简单的比喻来理解:想象你是一个项目经理,手下有5个开发人员,每人负责一个模块。只有当这5个模块都开发完成后,整个项目才能进入测试阶段。CountDownLatch
就相当于一个跟踪这5个模块开发进度的工具,每完成一个模块,计数器减一,当计数器变为0时,测试团队就可以开始工作了。
1.2 CountDownLatch
的核心方法
CountDownLatch
主要有两个核心方法:
- countDown():递减计数器,表示一个操作已完成
- await():等待计数器归零,如果计数器大于0,则当前线程进入等待状态,此外还有一些其他实用方法:
- await(long timeout, TimeUnit unit):等待计数器归零,但最多等待指定的时间
- getCount():获取当前计数器的值
1.3 基本使用示例
下面是一个简单的示例,演示 CountDownLatch
的基本使用:
import java.util.concurrent.CountDownLatch;public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 创建一个计数器为5的CountDownLatchCountDownLatch latch = new CountDownLatch(5);System.out.println("主线程开始等待所有工作线程完成...");// 创建并启动5个工作线程for (int i = 0; i < 5; i++) {final int workerId = i + 1;new Thread(() -> {try {// 模拟工作线程执行任务System.out.println("工作线程" + workerId + "开始执行任务");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作线程" + workerId + "完成任务");// 任务完成,计数器减一latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}// 主线程等待所有工作线程完成latch.await();System.out.println("所有工作线程已完成,主线程继续执行");}
}
在这个例子中,主线程创建了5个工作线程,然后调用latch.await()
等待所有工作线程完成。每个工作线程完成任务后调用latch.countDown()
将计数器减一。当所有5个工作线程都完成任务后,计数器归零,主线程从 await() 方法返回并继续执行。
二、CountDownLatch
实战应用
2.1 应用场景一:并行任务协调
CountDownLatch
最常见的应用场景是协调并行任务的执行。当一个复杂任务可以分解为多个独立的子任务并行执行时,我们可以使用 CountDownLatch
来等待所有子任务完成后再进行下一步操作。
下面是一个模拟并行加载系统模块的例子:
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SystemInitializer {public static void main(String[] args) throws InterruptedException {// 需要初始化的模块列表List<String> modules = Arrays.asList("数据库连接模块", "缓存服务模块", "消息队列模块", "用户认证模块", "日志服务模块");int moduleCount = modules.size();// 创建与模块数量相同的计数器CountDownLatch latch = new CountDownLatch(moduleCount);System.out.println("系统启动中,等待所有模块初始化...");// 创建固定大小的线程池ExecutorService executor = Executors.newFixedThreadPool(Math.min(moduleCount, 3));// 启动时间long startTime = System.currentTimeMillis();// 为每个模块创建初始化任务for (String module : modules) {executor.submit(() -> {try {initModule(module);} finally {// 模块初始化完成,计数器减一latch.countDown();System.out.println("模块 [" + module + "] 初始化完成,还剩 " + latch.getCount() + " 个模块");}});}// 等待所有模块初始化完成latch.await();// 计算总耗时long endTime = System.currentTimeMillis();System.out.println("所有模块初始化完成,系统启动成功!总耗时: " + (endTime - startTime) + "ms");// 关闭线程池executor.shutdown();}private static void initModule(String moduleName) {System.out.println("开始初始化模块: " + moduleName);try {// 模拟模块初始化耗时long sleepTime = (long) (Math.random() * 2000 + 1000);Thread.sleep(sleepTime);System.out.println("模块 [" + moduleName + "] 初始化耗时: " + sleepTime + "ms");} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("模块 [" + moduleName + "] 初始化被中断");}}
}
在这个例子中,我们使用 CountDownLatch
来协调系统各个模块的初始化过程。系统启动时需要初始化多个模块,每个模块的初始化可以并行进行,但只有当所有模块都初始化完成后,系统才能正常运行。
2.2 应用场景二:多阶段并发处理
有时我们需要将一个大型任务分为多个阶段,每个阶段都需要多个线程协同完成,且下一阶段必须等待上一阶段完全完成后才能开始。这种情况下,我们可以为每个阶段创建一个 CountDownLatch
。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MultiPhaseTask {public static void main(String[] args) throws InterruptedException {int threadCount = 3;// 创建线程池ExecutorService executor = Executors.newFixedThreadPool(threadCount);// 第一阶段的计数器CountDownLatch phase1Latch = new CountDownLatch(threadCount);// 第二阶段的计数器CountDownLatch phase2Latch = new CountDownLatch(threadCount);// 第三阶段的计数器CountDownLatch phase3Latch = new CountDownLatch(threadCount);System.out.println("开始执行多阶段任务...");// 启动所有工作线程for (int i = 0; i < threadCount; i++) {final int workerId = i + 1;executor.submit(() -> {try {// 第一阶段:数据准备System.out.println("工作线程" + workerId + "开始第一阶段:数据准备");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作线程" + workerId + "完成第一阶段");phase1Latch.countDown();// 等待所有线程完成第一阶段phase1Latch.await();System.out.println("所有线程完成第一阶段,工作线程" + workerId + "开始第二阶段");// 第二阶段:数据处理Thread.sleep((long) (Math.random() * 1500));System.out.println("工作线程" + workerId + "完成第二阶段");phase2Latch.countDown();// 等待所有线程完成第二阶段phase2Latch.await();System.out.println("所有线程完成第二阶段,工作线程" + workerId + "开始第三阶段");// 第三阶段:结果汇总Thread.sleep((long) (Math.random() * 800));System.out.println("工作线程" + workerId + "完成第三阶段");phase3Latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 等待所有阶段完成phase3Latch.await();System.out.println("所有阶段都已完成,任务结束");// 关闭线程池executor.shutdown();}
}
在这个例子中,我们将任务分为三个阶段:数据准备、数据处理和结果汇总。每个阶段都需要多个线程共同完成,且只有当所有线程都完成当前阶段后,才能进入下一阶段。
2.3 应用场景三:并发测试
CountDownLatch
在性能测试中也有重要应用,特别是需要模拟大量并发请求的场景。下面是一个模拟并发请求的例子:
import java.util.concurrent.CountDownLatch;startLatch.await();// 记录请求开始时间long startTime = System.currentTimeMillis();System.out.println("请求" + requestId + "开始执行");// 模拟发送HTTP请求boolean success = sendHttpRequest(requestId);// 记录请求结束时间并计算耗时long endTime = System.currentTimeMillis();long duration = endTime - startTime;// 根据请求结果更新计数器if (success) {successCount.incrementAndGet();System.out.println("请求" + requestId + "成功,耗时: " + duration + "ms");} else {failureCount.incrementAndGet();System.out.println("请求" + requestId + "失败,耗时: " + duration + "ms");}} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {// 请求完成,计数器减一endLatch.countDown();}});}// 记录测试开始时间long testStartTime = System.currentTimeMillis();// 发出开始信号,所有线程同时开始执行System.out.println("所有请求准备就绪,开始并发测试...");startLatch.countDown();// 等待所有请求完成boolean allCompleted = endLatch.await(30, TimeUnit.SECONDS);// 记录测试结束时间long testEndTime = System.currentTimeMillis();long totalDuration = testEndTime - testStartTime;// 输出测试结果System.out.println("\n并发测试完成!");if (!allCompleted) {System.out.println("警告:有些请求在超时时间内未完成");}System.out.println("总耗时: " + totalDuration + "ms");System.out.println("成功请求数: " + successCount.get());System.out.println("失败请求数: " + failureCount.get());System.out.println("平均响应时间: " + (totalDuration / concurrentRequests) + "ms");// 关闭线程池executor.shutdown();}// 模拟发送HTTP请求private static boolean sendHttpRequest(int requestId) {try {// 模拟请求处理时间,随机100-500msThread.sleep((long) (Math.random() * 400 + 100));// 模拟偶尔的请求失败,约5%的失败率return Math.random() > 0.05;} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}
}
在这个例子中,我们使用两个 CountDownLatch
:
startLatch
用于控制所有线程同时开始,模拟真实的并发请求场景。- endLatch 用于等待所有请求完成,以便统计整体测试结果。
这种方式可以准确测量系统在高并发情况下的性能表现,是性能测试的常用手段。
三、源码解析
了解 CountDownLatch
的内部实现原理,有助于我们更好地使用它。CountDownLatch
的实现基于 AQS(AbstractQueuedSynchronizer)框架,这也是 Java 并发包中许多同步器的基础。
3.1 CountDownLatch
的构造与字段
先看 CountDownLatch
的构造函数:
public CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);
}
CountDownLatch
内部维护了一个 Sync 实例,它是 AQS 的子类:
private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {setState(count);}int getCount() {return getState();}protected int tryAcquireShared(int acquires) {return (getState() == 0) ? 1 : -1;}protected boolean tryReleaseShared(int releases) {// Decrement count; signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}
}
Sync 类直接使用 AQS 的 state 变量作为计数器。构造函数中,将 state 设置为指定的计数值。
3.2 核心方法实现
3.2.1 await 方法
public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
await()
方法调用 AQS 的 acquireSharedInterruptibly()
,这个方法会调用 Sync 中重写的 tryAcquireShared()
方法。只有当计数器(state)为 0 时,tryAcquireShared()
才会返回正值,表示可以获取共享锁,否则线程将被阻塞。
3.2.2 countDown 方法
public void countDown() {sync.releaseShared(1);
}
countDown()
方法调用 AQS 的 releaseShared()
,这个方法会调用 Sync 中重写的 tryReleaseShared()
方法。tryReleaseShared()
通过 CAS
操作减少计数器的值,当计数器变为 0 时,会返回 true,此时 AQS 会释放所有等待的线程。
3.3 工作流程图解
CountDownLatch
的工作流程可以简化为以下几个步骤:
-
创建
CountDownLatch
对象,初始化计数器 -
等待线程调用 await() 方法,如果计数器大于 0,线程将被阻塞
-
工作线程完成任务后调用
countDown()
方法,计数器减一 -
当计数器减至 0 时,
AQS
会释放所有等待的线程,它们从await()
方法返回继续执行
四、CountDownLatch
的高级用法
4.1 带超时的等待
有时我们不希望无限期地等待所有操作完成,这时可以使用带超时参数的 await()
方法:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class CountDownLatchWithTimeout {public static void main(String[] args) {// 创建计数器为3的CountDownLatchCountDownLatch latch = new CountDownLatch(3);System.out.println("主线程开始等待工作线程完成...");// 启动工作线程for (int i = 0; i < 3; i++) {final int workerId = i + 1;new Thread(() -> {try {System.out.println("工作线程" + workerId + "开始执行");// 模拟工作线程耗时,第三个线程会故意执行很长时间long sleepTime = (workerId == 3) ? 5000 : 1000;Thread.sleep(sleepTime);System.out.println("工作线程" + workerId + "完成执行");latch.countDown();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}try {// 等待,但最多等待2秒boolean completed = latch.await(2, TimeUnit.SECONDS);if (completed) {System.out.println("所有工作线程在超时前完成了任务");} else {System.out.println("等待超时,但仍有" + latch.getCount() + "个任务未完成");System.out.println("主线程将继续执行,不再等待未完成的任务");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("主线程等待被中断");}System.out.println("主线程继续执行其他操作");}
}
在这个例子中,我们给 await()
方法设置了2秒的超时时间。由于第三个工作线程需要5秒才能完成,因此主线程会在等待2秒后继续执行,即使并非所有工作线程都已完成。
4.2 结合 CyclicBarrier 实现复杂同步
在更复杂的场景中,我们可能需要结合使用 CountDownLatch
和 CyclicBarrier 来实现多阶段、多方向的同步。例如,在一个分布式计算任务中,既需要等待所有工作节点准备就绪后才开始计算,又需要等待所有计算结果返回后才能进行汇总。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ComplexSynchronization {public static void main(String[] args) throws InterruptedException {int workerCount = 5;// 用于等待所有工作线程完成初始化CountDownLatch initLatch = new CountDownLatch(workerCount);// 用于等待所有工作线程完成计算CountDownLatch computeLatch = new CountDownLatch(workerCount);// 用于同步所有工作线程开始计算的时刻CyclicBarrier computeBarrier = new CyclicBarrier(workerCount, () -> {System.out.println("所有工作线程已就绪,开始并行计算");});ExecutorService executor = Executors.newFixedThreadPool(workerCount);System.out.println("主线程开始初始化工作线程...");for (int i = 0; i < workerCount; i++) {final int workerId = i + 1;executor.submit(() -> {try {// 初始化阶段System.out.println("工作线程" + workerId + "开始初始化");Thread.sleep((long) (Math.random() * 1000));System.out.println("工作线程" + workerId + "初始化完成");// 初始化完成,计数器减一initLatch.countDown();// 等待所有线程初始化完成,并同步开始计算computeBarrier.await();// 计算阶段System.out.println("工作线程" + workerId + "开始计算");Thread.sleep((long) (Math.random() * 2000));System.out.println("工作线程" + workerId + "计算完成");// 计算完成,计数器减一computeLatch.countDown();} catch (Exception e) {e.printStackTrace();}});}// 等待所有工作线程初始化完成System.out.println("主线程等待所有工作线程初始化完成...");initLatch.await();System.out.println("所有工作线程初始化完成,等待计算结果...");// 等待所有工作线程计算完成computeLatch.await();System.out.println("所有工作线程计算完成,开始汇总结果");// 模拟结果汇总Thread.sleep(500);System.out.println("结果汇总完成,任务结束");executor.shutdown();}
}
在这个例子中,我们使用了:
- initLatch(
CountDownLatch
)等待所有工作线程完成初始化。 - computeBarrier(CyclicBarrier)同步所有工作线程开始计算的时刻。
- computeLatch(
CountDownLatch
)等待所有工作线程完成计算。
这种组合使用可以实现更复杂的多阶段同步场景。
4.3 注意事项与最佳实践
在使用 CountDownLatch
时,有一些注意事项和最佳实践:
-
计数器不可重置:
CountDownLatch
的计数器一旦归零就不能重新设置,如果需要重复使用,应考虑CyclicBarrier
。 -
防止计数器错误:确保
countDown()
方法被正确调用,特别是在有异常的情况下,通常应该在finally
块中调用。 -
避免死锁:如果某些线程未能调用
countDown()
,等待的线程将永远阻塞,应考虑使用带超时的await()
方法。 -
资源释放:在所有线程都完成后,及时释放资源,如关闭线程池。
-
与线程池结合:通常应将
CountDownLatch
与线程池结合使用,而不是直接创建大量线程。
// 错误的使用方式(没有在finally中调用countDown)
public void wrongWay() {CountDownLatch latch = new CountDownLatch(1);executor.submit(() -> {try {// 如果这里抛出异常,countDown不会被调用doSomething();latch.countDown();} catch (Exception e) {e.printStackTrace();}});
}// 正确的使用方式
public void rightWay() {CountDownLatch latch = new CountDownLatch(1);executor.submit(() -> {try {doSomething();} catch (Exception e) {e.printStackTrace();} finally {// 确保即使出现异常,countDown也会被调用latch.countDown();}});
}
五、CountDownLatch
与其他同步工具的对比
为了更全面地理解 CountDownLatch
,我们将它与其他常见的同步工具进行对比:
5.1 CountDownLatch
vs CyclicBarrier
- 重用性:
CountDownLatch
的计数器减到0后就不能再用,而 CyclicBarrier 可以通过 reset() 方法重置,可以重复使用。 - 触发机制:
CountDownLatch
是一个线程等待多个线程,或多个线程等待一个线程;CyclicBarrier 是多个线程互相等待,直到所有线程都到达屏障点。 - 计数方式:
CountDownLatch
的计数器只能减少,CyclicBarrier 的计数器可以减少也可以重置。
5.2 CountDownLatch
vs Semaphore
- 作用范围:
CountDownLatch
主要用于等待事件;Semaphore 用于控制对资源的并发访问数量。 - 计数方向:
CountDownLatch
计数器只能递减直至归零;Semaphore 的计数器可以递增递减,只要不超过设定的最大值。 - 使用场景:
CountDownLatch
适用于一次性等待场景;Semaphore 适用于限制并发访问资源的场景。
5.3 CountDownLatch
vs Join
- 灵活性:
CountDownLatch
可以在任何时候调用 countDown(),而不必等待线程结束;Thread.join() 必须等待线程执行完成。 - 粒度:
CountDownLatch
可以实现更细粒度的控制,一个线程可以多次 countDown();join() 只能等待整个线程执行完毕。 - 中断处理:两者都支持中断,但
CountDownLatch
可以设置等待超时。
下面是一个简单的对比示例:
import java.util.concurrent.*;}private static void semaphoreExample() throws InterruptedException {// 创建只允许3个线程同时访问的信号量Semaphore semaphore = new Semaphore(3);ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {final int id = i;executor.submit(() -> {try {System.out.println("线程" + id + "等待获取许可");semaphore.acquire();System.out.println("线程" + id + "获得许可,开始执行");Thread.sleep(1000);System.out.println("线程" + id + "执行完毕,释放许可");semaphore.release();} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}// 给足够时间让任务执行Thread.sleep(5000);executor.shutdown();}private static void joinExample() throws InterruptedException {Thread[] threads = new Thread[3];for (int i = 0; i < threads.length; i++) {final int id = i;threads[i] = new Thread(() -> {try {System.out.println("线程" + id + "开始执行");Thread.sleep(1000 + id * 500);System.out.println("线程" + id + "执行完毕");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});threads[i].start();}System.out.println("等待所有线程完成...");for (Thread thread : threads) {thread.join();}System.out.println("所有线程已完成!");}
}
这个例子展示了CountDownLatch
、CyclicBarrier
、Semaphore
和Thread.join()
的不同使用方式和特点,有助于理解它们各自的应用场景。
六、面试中的 CountDownLatch
问题解析
CountDownLatch
是Java并发编程面试中的高频话题。下面列出一些常见面试问题及其答案:
6.1 基础概念题
问题1:什么是CountDownLatch
?它的主要用途是什么?
答:
CountDownLatch
是Java并发包中的同步工具类,允许一个或多个线程等待一系列指定操作的完成。它主要用于以下场景:
- 让主线程等待子线程完成再继续执行
- 实现多个线程之间的同步
- 控制并发测试中的并发量
- 多阶段并发任务的协调
它通过维护一个计数器来工作,每完成一个操作就减一,当计数器归零时释放所有等待的线程。
问题2:CountDownLatch
与CyclicBarrier的区别?
答:主要区别有:
重用性:
CountDownLatch
是一次性的,计数器归零后不能重置;CyclicBarrier可以通过reset()方法重置,可重复使用。计数方向:
CountDownLatch
计数器只能减少;CyclicBarrier的计数器是先减少,后自动重置。触发方式:
CountDownLatch
是调用countDown()方法;CyclicBarrier是调用await()方法。使用场景:
CountDownLatch
适用于一个或多个线程等待其他操作完成;CyclicBarrier适用于多个线程互相等待至某个状态,然后一起继续运行。
6.2 代码实现题
问题:实现一个高并发限流器,限制最大并发请求数为100,当请求处理完成后允许新的请求进入。
答:可以使用Semaphore实现最基本的限流功能,但结合
CountDownLatch
可以实现更加灵活的控制:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;public class RequestLimiter {// 最大并发数限制private final Semaphore semaphore;// 已完成的请求计数private final AtomicInteger completedRequests = new AtomicInteger(0);// 用于等待所有请求处理完成private CountDownLatch completionLatch;public RequestLimiter(int maxConcurrent) {this.semaphore = new Semaphore(maxConcurrent);}// 开始一轮请求处理public void startBatch(int requestCount) {this.completionLatch = new CountDownLatch(requestCount);this.completedRequests.set(0);}// 处理请求public void processRequest(Runnable task) throws InterruptedException {// 获取许可semaphore.acquire();try {// 执行请求处理task.run();} finally {// 释放许可semaphore.release();// 完成一个请求completedRequests.incrementAndGet();// 计数器减一completionLatch.countDown();}}// 等待所有请求处理完成public void awaitCompletion() throws InterruptedException {completionLatch.await();}// 获取已完成的请求数public int getCompletedRequestCount() {return completedRequests.get();}public static void main(String[] args) throws InterruptedException {RequestLimiter limiter = new RequestLimiter(10);int totalRequests = 100;// 开始处理100个请求limiter.startBatch(totalRequests);// 提交请求for (int i = 0; i < totalRequests; i++) {final int requestId = i;new Thread(() -> {try {System.out.println("请求 " + requestId + " 等待处理");limiter.processRequest(() -> {try {// 模拟请求处理System.out.println("处理请求 " + requestId);Thread.sleep((long) (Math.random() * 500));} catch (InterruptedException e) {Thread.currentThread().interrupt();}});System.out.println("请求 " + requestId + " 处理完成");} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}// 等待所有请求处理完成limiter.awaitCompletion();System.out.println("所有请求处理完成,共处理: " + limiter.getCompletedRequestCount() + " 个请求");}
}
6.3 原理分析题
问题:CountDownLatch
的内部实现原理是什么?它是如何实现线程等待和唤醒的?
答:
CountDownLatch
内部基于AQS(AbstractQueuedSynchronizer)
框架实现:
状态管理:使用
AQS
的state变量作为计数器等待机制:调用await()时,如果计数器不为0,则当前线程会被加入到
AQS
的等待队列中唤醒机制:调用
countDown()
时,计数器减1,当减至0时,AQS
会释放所有等待的线程线程安全性:通过
CAS(Compare And Swap)
操作保证计数器更新的原子性具体实现上,
CountDownLatch
包含一个继承自AQS
的内部类Sync
,它重写了tryAcquireShared
和tryReleaseShared
方法:
- tryAcquireShared:只有当计数器为0时才返回1,表示获取成功。
- tryReleaseShared:使用
CAS
操作安全地减少计数器,并在计数器变为0时返回true
相关文章:
CountDownLatch 并发编程中的同步利器
CountDownLatch 并发编程中的同步利器 文章目录 CountDownLatch 并发编程中的同步利器一、CountDownLatch 基础概念1.1 什么是 CountDownLatch?1.2 CountDownLatch 的核心方法1.3 基本使用示例 二、CountDownLatch 实战应用2.1 应用场景一:并行任务协调2…...
C++GO语言微服务之用户信息处理
目录 01 01-微服务实现用户注册-微服务端-上 02 02-微服务实现用户注册-微服务端-下 03 03-微服务实现用户注册-web端 04 04-微服务实现用户注册-web端-流程小结 05 05-获取地域信息-读MySQL写Redis入 06 06-获取地域信息-先查redis-没有读MySQL写入 01 07-Cookie简介 0…...
互联网大厂Java求职面试实战:Spring Boot微服务与数据库优化详解
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通 😁 2. 毕业设计专栏,毕业季咱们不慌忙,几百款毕业设计等你选。 ❤️ 3. Python爬虫专栏…...
Linux基本指令(一)
目录 基本指令 pwd指令 cd指令 cd ..编辑 cd ~ ls指令 ls -l ls -a ls -d touch指令 mkdir指令 rmdir指令 && rm 指令 操作系统是什么呢?一个好的操作系统要具备什么条件呢? 简单来说,操作系统是是一款做软硬件管理的软…...
Java—— 集合 List
List集合的特点 有序:存和取的元素顺序一致 有索引:可以通过索引操作元素 可重复:存储的元素可以重复 List集合的方法 Collection的方法List都继承了,可以使用Collection中的方法 此外,List集合因为有索引,…...
使用谱聚类将相似度矩阵分为2类
使用谱聚类将相似度矩阵分为2类的步骤如下: 构建相似度矩阵:提供的1717矩阵已满足对称性且对角线为1。 计算度矩阵:对每一行求和得到各节点的度,形成对角矩阵。 计算归一化拉普拉斯矩阵:采用对称归一化形式 LsymI−D…...
【Bootstrap V4系列】学习入门教程之 组件-表单(Forms)高级用法(二)
Bootstrap V4系列 学习入门教程之 组件-表单(Forms)高级用法(二) 表单(Forms)高级用法(二)一、Help text 帮助文本二、Disabled forms 禁用表单三、Validation 验证3.1 How it works…...
【许可证】Open Source Licenses
长期更新 扩展:shield.io装饰 开源许可证(Open Source Licenses)有很多种,每种都有不同的授权和限制,适用于不同目的。 默认的ISC🟰MIT License是否可商用是否要求开源衍生项目是否必须署名是否有专利授权…...
RT-Thread 深入系列 Part 7:RT-Thread vs 其他 RTOS 对比与选型指南
摘要 本文对比 RT-Thread、FreeRTOS、Zephyr、uC/OS、Mbed OS 等主流嵌入式操作系统,从功能特性、社区生态、学习成本、商业支持、安全性和典型应用场景六个维度进行详尽对比,并给出不同项目类型下的选型建议,帮助你为下一个嵌入式项目做出最合适的决策。 目录 功能特性对比…...
每天五分钟机器学习:KTT条件
本文重点 在前面的课程中,我们学习了拉格朗日乘数法求解等式约束下函数极值,如果约束不是等式而是不等式呢?此时就需要KTT条件出手了,KTT条件是拉格朗日乘数法的推广。KTT条件不仅统一了等式约束与不等式约束的优化问题求解范式,KTT条件给出了这类问题取得极值的一阶必要…...
金融学知识笔记
金融学知识笔记 一、引言 金融学它结合了数学、概率论、统计学、经济学和计算机科学等多学科的知识,用于解决金融领域中的各种问题,如金融衍生品定价、投资组合优化、风险管理和固定收益证券分析等。通过对金融学的学习,我们可以更好地理解…...
Web3 实战项目项目部署到 GitHub 和上线预览的完整指南
目录 🚀 一、部署到 GitHub ✅ 前置准备 🧱 部署步骤: 1. 创建一个 GitHub 仓库 2. 上传项目文件 方法一:使用 Git 命令行 方法二:直接上传 🌐 二、通过 GitHub Pages 免费上线 DApp(前端…...
嵌入式开发学习(阶段二 C语言基础)
C语言:第05天笔记 内容提要 分支结构 条件判断用if语句实现分支结构用switch语句实现分支结构 分支结构 条件判断 条件判断:根据某个条件成立与否,决定是否执行指定的操作。 条件判断的结果是逻辑值,也就是布尔类型值&#…...
【沉浸式求职学习day35】【Tomcat安装、配置】【Http简述】
😩😩😩,真的每天忙死,感觉自己精神上压力真的很大,希望我的努力能够激励到你们~ 沉浸式求职学习 Tomcat1.安装2.Tomcat启动和配置3.配置高难度面试题 4.发布一个web网站5.Http1.什么是HTTP2.两个时代3.Htt…...
基于大模型的新型隐球菌脑膜炎智能诊疗全流程系统设计与实现的技术方案文档
目录 一、术前风险预测系统1. 多模态融合模型架构2. 风险预测流程图(Mermaid)二、麻醉剂量预测系统1. 靶控输注(TCI)模型2. 麻醉方案优化流程图(Mermaid)三、术后并发症预测模型1. 时序预测模型(LSTM)2. 并发症预测流程图(Mermaid)四、健康教育管理模块1. 移动健康(…...
如何实现PLC远程编程
1. 什么是PLC编程 PLC编程是指为可编程逻辑控制器(Programmable Logic Controller,PLC)编写控制逻辑的过程。PLC是一种工业自动化控制设备,广泛应用于制造业、机械控制、流水线、电力系统等领域,用于替代传统的继电器…...
Go基于plugin的热更新初体验
背景 对于一个部署在生产环境的项目来说,我们希望当代码出现bug的时候,可以不用重启进程而达到动态修改代码的目的—— 这就是代码热部署! 使用java做游戏服务器,最大的好处是,当代码出现bug,可以直接热…...
【STM32 学习笔记】I2C通信协议
注:通信协议的设计背景 3:00~10:13 I2C 通讯协议(Inter-Integrated Circuit)是由Phiilps公司开发的,由于它引脚少,硬件实现简单,可扩展性强, 不需要USART、CAN等通讯协议的外部收发设备,现在被广…...
RAG与语义搜索:让大模型成为测试工程师的智能助手
引言 AI大模型风头正劲,自动生成和理解文本的能力让无数行业焕发新生。测试工程师也不例外——谁不想让AI自动“看懂需求、理解接口、生成用例”?然而,很多人发现:直接丢问题给大模型,答案貌似“懂行”,细…...
Nakama:让游戏与应用更具互动性和即时性
在现代游戏和应用程序开发中,实现社交互动和实时功能已成为用户体验的核心需求。为满足这种需求,许多开发者正转向分布式服务器技术,在这些技术中,Nakama 构建起了一座桥梁。Nakama 是一个开源的分布式服务器,专门为社…...
[Spring AOP 7] 动态通知调用
动态通知调用 本笔记基于黑马程序员 Spring高级源码解读 更美观清晰的版本在:Github 注意:阅读本章内容之前,建议先熟悉静态通知调用的内容 在静态通知调用一节中,我们还有一个悬而未决的问题。 我们谈到了调用链MethodInvocation…...
Redis 集群
集群基本介绍 广义的集群,只要是多个机器构成了分布式系统,都可以称为是一个“集群”(主从模式,哨兵模式) 狭义的集群,Redis 提供的集群模式,在这个模式下主要解决的是存储空间不足的问题&…...
【Redis】基础命令数据结构
文章目录 基础命令keysexistsdelexpirettltype 数据结构和内部编码 在介绍数据类型前先介绍一下 Redis 的基础命令,方便理解 基础命令 keys 返回所有满足样式(pattern)的 key keys pattern 当前有如下 key PS:实际开发环境和生…...
C++(6):逻辑运算符
目录 1. 代码示例 示例 1:基础用法 示例 2:条件判断 2. 短路求值(Short-Circuit Evaluation) 代码示例 3. 实际应用场景 场景 1:输入合法性验证 场景 2:游戏状态判断 4. 注意事项 逻辑运算符用于组…...
项目管理从专家到小白
敏捷开发 Scrum 符合敏捷开发原则的一种典型且在全球使用最为广泛的框架。 三个角色 产品负责人Product Ower:专注于了解业务、客户和市场要求,然后相应地确定工程团队需要完成的工作的优先顺序。 敏捷教练Scrum Master:确保 Scrum 流程顺…...
AI绘画灵感觉醒指南:从灵感源泉到创意输出
目录 一、引言 二、灵感来源 2.1 现实生活 2.2 其他艺术作品 2.3 文学作品 三、灵感转化为输入提示 3.1 明确主题与核心元素 3.2 细化描述 3.3 使用修饰词与专业术语 3.4 组合与优化提示词 四、案例分析 4.1 从现实生活获取灵感 4.2 从其他艺术作品获取灵感 4.3 …...
【Java学习】枚举(匿名类详解)
目录 一、匿名类 1.形式 2.性质 2.1匿名性 2.1.1同步性 使用场景 2.1.2复用性 2.1.3向上转型 2.2实现性 3.传参 3.1构造传全参 3.1.1过程 3.1.2效果 2.1.4原子类构造无参 4.权限 二、枚举类 1.枚举常量 2.性质 2.1多态性 2.2单例性 2.2.1private保护 2.2…...
力扣题解:2、两数相加
个人认为,该题目可以看作合并两个链表的变种题,本题与21题不同的是,再处理两个结点时,对比的不是两者的大小,而是两者和是否大于10,加法计算中大于10要进位,所以我们需要声明一个用来标记是否进…...
PyTorch API 9 - masked, nested, 稀疏, 存储
文章目录 torch.randomtorch.masked简介动机什么是 MaskedTensor? 支持的运算符一元运算符二元运算符归约操作查看与选择函数 torch.nested简介构造方法数据布局与形状支持的操作查看嵌套张量的组成元素填充张量的相互转换形状操作注意力机制 与 torch.compile 的配…...
linux测试硬盘读写速度
#!/bin/bash # 文件名: disk_rate.sh # linux测试硬盘读写速度 TEST_FILE"disk_speed_test.tmp" TEST_SIZE"1024M" echo "开始测试磁盘写入速度..." WRITE_RESULT$(dd if/dev/zero of$TEST_FILE bs$TEST_SIZE count1 oflagdirect 2…...
单片机系统设计不同开发方式的优缺点(面包板,洞洞板,PCB板)
目录 快速验证代码逻辑 涉及具体电路较多 涉及高频电路 快速验证代码逻辑 面包板,无焊接,适合快速搭建临时电路。优点应该是使用方便,不需要焊接,可以随时更换元件。但缺点可能是不稳定,接触不良,不适合高…...
数字信号处理|| 快速傅里叶变换(FFT)
一、实验目的 (1)加深对快速傅里叶变换(FFT)基本理论的理解。 (2)了解使用快速傅里叶变换(FFT)计算有限长序列和无限长序列信号频谱的方法。 (3)掌握用MATLA…...
基于CNN卷积神经网络的带频偏QPSK调制信号检测识别算法matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2024b 3.部分核心程序 (完整版代码包含详细中文注释和操作步骤视频)…...
数据库索引详解:原理 · 类型 · 使用 · 优化
在关系型数据库中,索引(Index)是提高查询性能的利器。合理设计和使用索引,可以极大地减少 IO 操作,提升查询效率;但滥用或误用索引,却可能带来维护开销和性能瓶颈。我将从以下几个方面ÿ…...
Java大数据可视化在城市空气质量监测与污染溯源中的应用:GIS与实时数据流的技术融合
随着城市化进程加速,空气质量监测与污染溯源成为智慧城市建设的核心议题。传统监测手段受限于数据离散性、分析滞后性及可视化能力不足,难以支撑实时决策。2025年4月27日发布的《Java大数据可视化在城市空气质量监测与污染溯源中的应用》一文,…...
【基于 LangChain 的异步天气查询3】OpenWeather实现实时天气查询
目录 一、项目功能概述 1、城市识别(GeoNames API) 2、天气数据获取(OpenWeather API) 3、AI 分析天气(deepseek-r1) 4、异步运行支持 5、配置文件隔离(.env) 二、注册 OpenW…...
.Net HttpClient 管理客户端(初始化与生命周期管理)
HttpClient 初始化与生命周期管理 HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。 为实现复用,HttpClient类库默认使用连接池和请求管道,可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHt…...
树莓派4的v4l2摄像头(csi)no cameras available,完美解决
根据2025年最新技术文档和树莓派官方支持建议,no cameras available错误通常由驱动配置冲突或硬件连接问题导致。以下是系统化解决方案: 一、核心修复步骤 强制禁用传统驱动 sudo nano /boot/firmware/config.txt确保包含以下配置(2025年新版…...
VBA将PDF文档内容逐行写入Excel
VBA是无法直接读取PDF文档的,但结合上期我给大家介绍了PDF转换工具xpdf-tools-4.05,先利用它将PDF文档转换为TXT文档,然后再将TXT的内容写入Excel,这样就间接实现了将PDF文档的内容导入Excel的操作。下面的代码将向大家演示如何实…...
【STM32 学习笔记】USART串口
注意:在串口助手的接收模式中有文本模式和HEX模式两种模式,那么它们有什么区别? 文本模式和Hex模式是两种不同的文件编辑或浏览模式,不是完全相同的概念。文本模式通常是指以ASCII编码格式表示文本文件的编辑或浏览模式。在文…...
位图布隆过滤器
1.位图 所谓位图,就是用每一位来存放某种状态,适用于海量数据,整数,数据无重复的场景。通常是用来判 断某个数据存不存在的。 如上例子,10个整数本应该存放需要40个字节,此时用位图只需要3个字节。 下面代…...
【Web】使用Vue3开发鸿蒙的HelloWorld!
文章目录 1、简介2、效果3、环境3.1、开发环境3.2、运行环境 4、实现4.1、在VSCode上使用Vue开发HelloWorld4.1.1、通过 Vite 快速创建项目4.1.2、修改 src/App.vue4.1.3、模拟Web浏览器运行 4.2、使用DevEco完成手机App端移植4.2.1、构建Vue 3项目为静态文件4.2.2、创建Harmon…...
cv_area_center()
主题 用opencv实现了halcon中area_center算子的功能, 返回region的面积,中心的行坐标和中心的列坐标 代码很简单 def cv_area_center(region):area[]row []col []for re in region:retval cv2.moments(re)area.append(retval[m00])row.append(int(r…...
Python+OpenCV实现手势识别与动作捕捉:技术解析与应用探索
引言:人机交互的新维度 在人工智能与计算机视觉技术飞速发展的今天,手势识别与动作捕捉技术正逐步从实验室走向大众生活。通过Python的OpenCV库及MediaPipe等工具,开发者能够以较低门槛实现精准的手部动作识别,为虚拟现实、智能家…...
【llama-factory】Lora微调和DPO训练
微调参考 DPO参考 llama-factory官网 LoRA微调 数据集处理 数据集格式和注册 Alpaca数据集格式: [{"instruction": "人类指令(必填)","input": "人类输入(选填)","…...
JS较底层的用法,几类简单介绍
JS较底层的用法 在 JavaScript 中,“偏底层”的用法通常是指更接近语言核心、规范、底层机制的特性。这些用法不是日常开发中最常见的,但对理解语言原理、优化性能或构建框架、库非常重要。下面是一些常见的“偏底层”用法或特性 1. 对象属性底层操作&am…...
当可视化遇上 CesiumJS:突破传统,打造前沿生产配套方案
CesiumJS 技术基础介绍 CesiumJS 是一款基于 JavaScript 的开源库,专门用于创建动态、交互式的地理空间可视化。它利用 WebGL 技术,能够在网页浏览器中流畅地渲染高分辨率的三维地球和地图场景。CesiumJS 支持多种地理空间数据格式,包括但不…...
使用python脚本连接SQL Server数据库导出表结构
一. 准备工作 Mac 系统安装freetds brew install freetds 安装pymssql pip3 install pymssql 二.导出指定表的结构: import pymssql# 配置数据库连接参数(根据实际情况修改) server # 内网服务器地址或IP database # 数据库名称…...
Docker基础入门
Docker核心概念 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。 容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app&#…...
day011-权限管理专题
文章目录 1. 对比文件内容1.1 diff1.2 vimdiff 2. /etc/skel目录3. 权限基础4. 修改权限4.1 用数字权限修改4.2 用字母修改权限(ugo)4.3 修改文件所有者和用户组 5. 文件与目录权限6. permission denied 权限拒绝7. 特殊权限8. 特殊属性9. 思维导图 1. 对…...