在多线程编程中,线程间的协调是一个核心问题。如何让多个线程按照特定规则执行?如何等待多个线程都完成任务后再进行下一步?如何控制同时访问资源的线程数量?JDK 中为我们提供了三个强大的同步工具类来解决这些问题:CountDownLatch、CyclicBarrier 和 Semaphore。
这篇文章将通过实际案例和图解,带你深入理解这三个工具类的使用方法和应用场景,让你的多线程编程更加得心应手。
1. CountDownLatch:等待多任务完成的倒计时门闩
1.1 什么是 CountDownLatch?
CountDownLatch(倒计时门闩)是一种同步辅助工具,它允许一个或多个线程等待,直到其他线程完成一组操作后才继续执行。
1.2 CountDownLatch 核心 API
CountDownLatch(int count)
:构造方法,初始化计数器值await()
:等待计数器归零,会阻塞当前线程await(long timeout, TimeUnit unit)
:带超时的等待countDown()
:计数器减 1getCount()
:获取当前计数器的值
1.3 实战案例:模拟数据加载
假设我们正在开发一个应用程序,需要从多个数据源加载数据,只有当所有数据都加载完成后,才能进行数据整合和展示。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DataLoadingExample {
public static void main(String[] args) throws InterruptedException {
// 创建CountDownLatch,计数器设置为3(对应三个数据源)
CountDownLatch latch = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);
// 记录开始时间
long start = System.currentTimeMillis();
// 模拟从三个不同数据源加载数据
executor.submit(() -> loadDataFromSource("数据库", 3000, latch));
executor.submit(() -> loadDataFromSource("Redis", 2000, latch));
executor.submit(() -> loadDataFromSource("API", 1500, latch));
System.out.println("等待所有数据源加载完成...");
// 等待所有数据源加载完成
latch.await();
// 记录完成时间
long end = System.currentTimeMillis();
System.out.println("所有数据源加载完成,总耗时: " + (end - start) + "ms");
System.out.println("开始处理整合的数据...");
executor.shutdown();
}
private static void loadDataFromSource(String source, int sleepTime, CountDownLatch latch) {
try {
System.out.println("开始从" + source + "加载数据...");
// 模拟耗时操作
Thread.sleep(sleepTime);
System.out.println(source + "数据加载完成");
} catch (InterruptedException e) {
e.printStackTrace();
// 注意:即使任务执行失败,我们也调用countDown()
// 这里假设我们的业务逻辑要求:无论成功还是失败,都视为该数据源加载过程已结束
// 在实际应用中,可能需要根据业务需求决定失败时是否调用countDown()
} finally {
// 完成后计数器减1
latch.countDown();
System.out.println(source + "数据源加载完成,通知CountDownLatch计数器减1,当前计数:" + latch.getCount());
}
}
}
输出结果:
等待所有数据源加载完成...
开始从数据库加载数据...
开始从Redis加载数据...
开始从API加载数据...
API数据加载完成
API数据源加载完成,通知CountDownLatch计数器减1,当前计数:2
Redis数据加载完成
Redis数据源加载完成,通知CountDownLatch计数器减1,当前计数:1
数据库数据加载完成
数据库数据源加载完成,通知CountDownLatch计数器减1,当前计数:0
所有数据源加载完成,总耗时: 3004ms
开始处理整合的数据...
这个例子中:
- 创建了一个计数值为 3 的 CountDownLatch
- 启动 3 个线程分别从不同数据源加载数据
- 主线程调用
latch.await()
等待 - 每个工作线程完成数据加载后调用
countDown()
- 当计数器归零后,主线程从 await()方法返回,继续执行后续逻辑
1.4 CountDownLatch 原理解析
CountDownLatch 内部基于 AQS(AbstractQueuedSynchronizer)实现,使用 AQS 的共享模式。当创建一个 CountDownLatch 实例时,会传入一个初始计数值,这个值会被保存在 AQS 的 state 变量中:
await()
方法会先检查 state 是否为 0,如果是则继续执行,否则将当前线程加入等待队列countDown()
方法会将 state 值减 1,当减到 0 时,会唤醒所有在 await()上等待的线程- CountDownLatch 使用 AQS 的共享模式,允许多个线程同时获取同步状态
1.5 CountDownLatch 的注意事项
- 计数器不能重置:CountDownLatch 的计数器无法重置,一旦计数到 0,就不能再用了
- 只能等待一次性事件:适合等待一次性事件,不适合周期性重复的场景
- countDown()可以在任何线程中调用:不一定要在执行任务的线程中调用
- 注意处理中断异常:await()方法会抛出 InterruptedException
- 可能导致永久等待:如果某个任务没有正确调用 countDown(),可能导致等待线程永远阻塞
- 避免 countDown()调用次数超过初始计数:虽然这不会导致异常,但它表明程序逻辑可能存在问题
2. CyclicBarrier:可重用的线程同步屏障
2.1 什么是 CyclicBarrier?
CyclicBarrier(循环屏障)是一种同步辅助工具,它允许多个线程在某个点上互相等待,当所有线程都到达这个点后,屏障打开,所有线程继续执行。与 CountDownLatch 不同,CyclicBarrier 可以重复使用。
2.2 CyclicBarrier 核心 API
CyclicBarrier(int parties)
:创建一个屏障,等待指定数量的线程CyclicBarrier(int parties, Runnable barrierAction)
:创建一个屏障,并在所有线程到达时执行 barrierActionawait()
:等待所有线程到达屏障点await(long timeout, TimeUnit unit)
:带超时的等待reset()
:重置屏障getNumberWaiting()
:获取当前在屏障处等待的线程数isBroken()
:查询屏障是否被破坏
2.3 实战案例:多阶段计算任务
假设我们有一个复杂的计算任务,可以分成三个阶段,每个阶段都需要多个线程协同计算,只有当所有线程完成当前阶段后,才能一起进入下一阶段。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MultiStageCalculation {
public static void main(String[] args) {
int workerCount = 3;
// 创建CyclicBarrier,所有线程到达屏障时会执行指定的操作
CyclicBarrier barrier = new CyclicBarrier(workerCount, () -> {
System.out.println("======= 所有线程完成当前阶段,准备进入下一阶段 =======");
});
ExecutorService executor = Executors.newFixedThreadPool(workerCount);
for (int i = 0; i < workerCount; i++) {
final int workerId = i;
executor.submit(() -> {
try {
// 第一阶段:数据准备
System.out.println("工作线程" + workerId + "开始准备数据...");
Thread.sleep(1000 + (int)(Math.random() * 1000));
System.out.println("工作线程" + workerId + "完成数据准备");
barrier.await(); // 等待所有线程完成数据准备
// 第二阶段:数据计算
System.out.println("工作线程" + workerId + "开始数据计算...");
Thread.sleep(2000 + (int)(Math.random() * 1000));
System.out.println("工作线程" + workerId + "完成数据计算");
barrier.await(); // 等待所有线程完成数据计算
// 第三阶段:结果汇总
System.out.println("工作线程" + workerId + "开始汇总结果...");
Thread.sleep(1000 + (int)(Math.random() * 1000));
System.out.println("工作线程" + workerId + "完成结果汇总");
barrier.await(); // 等待所有线程完成结果汇总
System.out.println("工作线程" + workerId + "所有任务完成!");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
输出结果:
工作线程0开始准备数据...
工作线程1开始准备数据...
工作线程2开始准备数据...
工作线程1完成数据准备
工作线程0完成数据准备
工作线程2完成数据准备
======= 所有线程完成当前阶段,准备进入下一阶段 =======
工作线程1开始数据计算...
工作线程2开始数据计算...
工作线程0开始数据计算...
工作线程0完成数据计算
工作线程1完成数据计算
工作线程2完成数据计算
======= 所有线程完成当前阶段,准备进入下一阶段 =======
工作线程0开始汇总结果...
工作线程2开始汇总结果...
工作线程1开始汇总结果...
工作线程2完成结果汇总
工作线程0完成结果汇总
工作线程1完成结果汇总
======= 所有线程完成当前阶段,准备进入下一阶段 =======
工作线程1所有任务完成!
工作线程0所有任务完成!
工作线程2所有任务完成!
在这个例子中:
- 创建了一个 parties 为 3 的 CyclicBarrier,并指定了屏障动作
- 三个工作线程分别执行三个阶段的任务
- 每完成一个阶段,线程就会调用
barrier.await()
等待其他线程 - 当所有线程都到达屏障点,最后到达的线程会执行屏障动作,然后所有线程被唤醒并继续下一阶段
- 整个过程会重复直到所有阶段完成
2.4 CyclicBarrier 原理解析
CyclicBarrier 也是基于 AQS 实现的,但它使用的是 ReentrantLock 和 Condition,这与 CountDownLatch 的直接使用 AQS 不同:
- 内部使用
ReentrantLock
和Condition
实现线程同步 - 维护计数器,记录还未到达屏障的线程数
- 当线程调用
await()
时,计数器减 1 - 如果计数器不为 0,当前线程进入等待状态
当最后一个线程到达屏障点,计数器变为 0
- 执行屏障动作(如果有)
- 重置计数器为初始值
- 唤醒所有等待的线程
2.5 CyclicBarrier 的注意事项
- 可以重复使用:与 CountDownLatch 不同,CyclicBarrier 可以通过自动重置或手动调用 reset()方法重置
- 必须所有线程都调用 await():如果有线程没有调用 await(),可能导致其他线程永久等待
- 处理中断和超时情况:await()方法会抛出 InterruptedException 和 BrokenBarrierException
- 注意屏障破坏:当有线程中断或超时,屏障会被破坏(broken),需要调用 reset()重置
- parties 值必须与参与线程数匹配:否则可能导致不必要的等待或永久阻塞
3. Semaphore:控制并发访问的信号量
3.1 什么是 Semaphore?
Semaphore(信号量)是一种计数信号量,用于控制同时访问特定资源的线程数量。它维护了一组许可证,线程在访问资源前必须获取许可,用完后释放。
3.2 Semaphore 核心 API
Semaphore(int permits)
:创建指定许可数的信号量,默认非公平模式Semaphore(int permits, boolean fair)
:创建信号量,可指定是否公平acquire()
:获取一个许可,如果没有可用的许可则阻塞acquire(int permits)
:获取指定数量的许可tryAcquire()
:尝试获取许可,立即返回成功或失败tryAcquire(long timeout, TimeUnit unit)
:带超时的尝试获取release()
:释放一个许可release(int permits)
:释放指定数量的许可availablePermits()
:返回当前可用的许可数
3.3 实战案例:实现数据库连接池
假设我们要设计一个简单的数据库连接池,限制同时活动的连接数,防止连接数过多导致数据库压力过大。
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
public class DatabaseConnectionPool {
// 连接池大小
private final int MAX_CONNECTIONS = 10;
// 信号量,控制允许的并发连接数
private final Semaphore semaphore;
// 连接列表
private final List<JdbcConnection> connectionList = new ArrayList<>();
// 当前已创建的连接数
private final AtomicInteger createdConnections = new AtomicInteger(0);
public DatabaseConnectionPool() {
// 创建信号量,设置最大许可数为连接池大小
this.semaphore = new Semaphore(MAX_CONNECTIONS);
}
// 获取数据库连接
public JdbcConnection getConnection() throws InterruptedException {
// 获取许可
semaphore.acquire();
// 获取或创建连接
return getOrCreateConnection();
}
// 释放连接
public void releaseConnection(JdbcConnection connection) {
if (connection != null) {
// 将连接放回池中
returnConnectionToPool(connection);
// 释放许可
semaphore.release();
System.out.println("释放连接: " + connection.getId() + ", 释放许可后当前可用许可: " + semaphore.availablePermits());
}
}
// 获取或创建连接
// 注:在实际应用中,访问connectionList的操作需要同步处理
// 这里为简化示例,使用synchronized。在高并发场景下可考虑使用ConcurrentHashMap等并发容器
private synchronized JdbcConnection getOrCreateConnection() {
// 首先尝试从池中获取可用连接
for (JdbcConnection conn : connectionList) {
if (!conn.isInUse()) {
conn.setInUse(true);
System.out.println("复用连接: " + conn.getId() + ", 当前可用许可: " + semaphore.availablePermits());
return conn;
}
}
// 如果没有可用连接且未达到最大连接数,则创建新连接
if (createdConnections.get() < MAX_CONNECTIONS) {
JdbcConnection newConn = createNewConnection();
connectionList.add(newConn);
System.out.println("创建新连接: " + newConn.getId() + ", 当前可用许可: " + semaphore.availablePermits());
return newConn;
}
// 这种情况理论上不会发生,因为Semaphore控制了并发量
throw new IllegalStateException("无法获取连接");
}
// 创建新连接
private JdbcConnection createNewConnection() {
int id = createdConnections.incrementAndGet();
// 模拟创建JDBC连接
return new JdbcConnection(id);
}
// 将连接放回池中
private void returnConnectionToPool(JdbcConnection connection) {
connection.setInUse(false);
}
// 获取当前可用许可数
public int getAvailablePermits() {
return semaphore.availablePermits();
}
// 获取已创建的连接数
public int getCreatedConnectionsCount() {
return createdConnections.get();
}
// 模拟数据库连接类
public static class JdbcConnection {
private final int id;
private boolean inUse;
public JdbcConnection(int id) {
this.id = id;
this.inUse = true;
}
public int getId() {
return id;
}
public boolean isInUse() {
return inUse;
}
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
// 模拟执行SQL
public void executeQuery(String sql) {
System.out.println("连接" + id + "执行SQL: " + sql);
}
}
// 测试连接池
public static void main(String[] args) {
DatabaseConnectionPool pool = new DatabaseConnectionPool();
// 创建20个线程,尝试从连接池获取连接
for (int i = 0; i < 20; i++) {
final int userId = i;
new Thread(() -> {
try {
System.out.println("用户" + userId + "尝试获取数据库连接...");
JdbcConnection conn = pool.getConnection();
System.out.println("用户" + userId + "获取到连接" + conn.getId());
// 模拟使用连接执行SQL
conn.executeQuery("SELECT * FROM users WHERE id = " + userId);
// 模拟操作耗时
Thread.sleep(2000 + (int)(Math.random() * 3000));
// 释放连接
pool.releaseConnection(conn);
System.out.println("用户" + userId + "释放了连接");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 控制创建线程的速度
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果(部分输出):
用户0尝试获取数据库连接...
创建新连接: 1, 当前可用许可: 9
用户0获取到连接1
连接1执行SQL: SELECT * FROM users WHERE id = 0
用户1尝试获取数据库连接...
创建新连接: 2, 当前可用许可: 8
用户1获取到连接2
连接2执行SQL: SELECT * FROM users WHERE id = 1
...
用户9尝试获取数据库连接...
创建新连接: 10, 当前可用许可: 0
用户9获取到连接10
连接10执行SQL: SELECT * FROM users WHERE id = 9
用户10尝试获取数据库连接...
用户0释放了连接
释放连接: 1, 释放许可后当前可用许可: 1
用户10获取到连接1
连接1执行SQL: SELECT * FROM users WHERE id = 10
...
在这个例子中:
- 创建了一个 permits 为 10 的 Semaphore,控制同时活动的连接数上限
- 每次获取连接前,必须先获取一个许可
- 使用完连接后,释放许可
- 当所有许可都被获取,新的请求会被阻塞,直到有许可被释放
- 连接池复用已创建的连接,提高效率
3.4 Semaphore 原理解析
Semaphore 也是基于 AQS 实现的,支持公平和非公平两种模式,其工作原理如下:
acquire()
方法会尝试将 AQS 的 state 值减 1,如果 state 大于 0 则成功获取许可,否则线程进入等待队列release(n)
方法会将 state 值加 n,并尝试唤醒等待队列中的线程- 公平模式下,线程按照 FIFO 顺序获取许可
- 非公平模式下,新到的线程可以"插队"获取许可
3.5 Semaphore 的注意事项
- 始终在 finally 块中释放许可:防止程序异常导致许可泄漏,许可泄漏会导致资源耗尽
- 注意公平性选择:非公平模式性能更好,但可能导致某些线程长时间等待
- 可以释放"非自己获取"的许可:任何线程都可以调用 release(),即使它没有调用过 acquire()
- 注意许可使用数量一致:如果 acquire(n),应该对应 release(n)
- 避免信号量值过大:信号量值过大可能导致资源过度使用,失去控制意义
4. 三大同步工具类的对比与选择
让我们对这三个同步工具类进行对比,以便在不同场景下做出正确选择:
4.1 功能对比
特性 | CountDownLatch | CyclicBarrier | Semaphore |
---|---|---|---|
主要功能 | 等待其他线程完成 | 线程之间相互等待 | 控制并发访问量 |
计数方向 | 递减到 0(一次性) | 递减到 0 后重置(循环) | 动态增减(许可) |
重置能力 | 不可重置 | 可以重置 | 可动态调整许可 |
线程等待 | 单方向等待(一方等多方) | 多方互相等待 | 竞争许可的阻塞 |
典型使用场景 | 主线程等待多个子任务 | 多线程分阶段执行 | 有限资源访问控制 |
屏障动作 | 无 | 所有线程到达时执行 | 无 |
AQS 实现方式 | 直接使用 AQS 共享模式 | 基于 ReentrantLock 和 Condition | 使用 AQS 共享模式 |
4.2 应用场景对比
CountDownLatch 适用场景:
- 主线程需要等待多个工作线程完成初始化
- 等待多个服务启动完成后再对外提供服务
- 异步任务的结果统一处理
- 不适用场景:需要重复使用同一个同步点;需要执行复杂的屏障动作
CyclicBarrier 适用场景:
- 多阶段计算任务,每个阶段都需要所有线程完成
- 并行迭代算法中的同步点
- 多个线程需要相互等待的场景
- 不适用场景:单向等待(一个线程等待多个线程);不确定参与线程数量的场景
Semaphore 适用场景:
- 限制对有限资源的并发访问(如连接池)
- 实现流量控制或限流
- 提高对有限资源利用率(如线程池)
- 不适用场景:需要线程同步执行或互相等待;需要保证任务执行顺序
5. 实际应用场景扩展
5.1 复杂场景:CountDownLatch 与 CyclicBarrier 结合使用
在一些复杂场景中,我们可能需要结合使用这些工具:
import java.util.concurrent.*;
public class CombinedSyncExample {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
int stageCount = 2;
// 主线程等待所有工作线程完成
CountDownLatch endLatch = new CountDownLatch(threadCount);
// 每个阶段的同步点
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("======= 当前阶段所有线程到达,进入下一阶段 =======");
});
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
final int workerId = i;
executor.submit(() -> {
try {
for (int stage = 0; stage < stageCount; stage++) {
// 第stage+1阶段开始
System.out.println("工作线程" + workerId + "执行第" + (stage + 1) + "阶段任务");
Thread.sleep(1000 + (int)(Math.random() * 1000));
// 等待所有线程完成当前阶段
System.out.println("工作线程" + workerId + "完成第" + (stage + 1) + "阶段,等待其他线程");
barrier.await();
}
// 所有阶段都完成,工作线程结束
System.out.println("工作线程" + workerId + "所有任务完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 通知主线程当前工作线程已完成
endLatch.countDown();
}
});
}
// 主线程等待所有工作线程完成
System.out.println("主线程等待所有工作线程完成...");
endLatch.await();
System.out.println("所有工作线程已完成,主线程继续执行");
executor.shutdown();
}
}
5.2 Semaphore 实现令牌桶限流器
利用 Semaphore 可以轻松实现一个令牌桶限流器:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class TokenBucketRateLimiter {
private final Semaphore semaphore;
private final int capacity;
private final int ratePerSecond;
private final ScheduledExecutorService scheduler;
// 请求计数
private final AtomicInteger requestCount = new AtomicInteger(0);
private final AtomicInteger successCount = new AtomicInteger(0);
private final AtomicInteger failureCount = new AtomicInteger(0);
public TokenBucketRateLimiter(int capacity, int ratePerSecond) {
this.capacity = capacity;
this.ratePerSecond = ratePerSecond;
this.semaphore = new Semaphore(capacity);
this.scheduler = Executors.newScheduledThreadPool(1);
// 定时添加令牌
startTokenScheduler();
}
private void startTokenScheduler() {
scheduler.scheduleAtFixedRate(() -> {
int permitsToAdd = Math.min(ratePerSecond, capacity - semaphore.availablePermits());
if (permitsToAdd > 0) {
semaphore.release(permitsToAdd);
System.out.println("添加" + permitsToAdd + "个令牌,当前可用令牌: " + semaphore.availablePermits());
}
}, 1, 1, TimeUnit.SECONDS);
}
// 尝试获取访问权限
public boolean tryAcquire() {
requestCount.incrementAndGet();
boolean acquired = semaphore.tryAcquire();
if (acquired) {
successCount.incrementAndGet();
} else {
failureCount.incrementAndGet();
}
return acquired;
}
// 打印统计信息
public void printStats() {
int total = requestCount.get();
int success = successCount.get();
int failure = failureCount.get();
System.out.println("总请求数: " + total + ", 成功: " + success + ", 被限流: " + failure +
", 成功率: " + (total > 0 ? (success * 100.0 / total) : 0) + "%");
}
public void shutdown() {
scheduler.shutdown();
}
// 测试限流器
public static void main(String[] args) throws InterruptedException {
// 创建限流器,容量10,每秒补充5个令牌
TokenBucketRateLimiter limiter = new TokenBucketRateLimiter(10, 5);
// 创建一个线程池模拟请求
ExecutorService service = Executors.newFixedThreadPool(20);
// 记录开始时间
long startTime = System.currentTimeMillis();
// 模拟30秒的请求流量,每100ms发送一个请求
for (int i = 0; i < 300; i++) {
final int requestId = i;
service.submit(() -> {
// 尝试获取令牌
boolean allowed = limiter.tryAcquire();
System.out.println("请求" + requestId + ": " + (allowed ? "通过" : "被限流") +
", 当前可用令牌: " + limiter.semaphore.availablePermits());
if (allowed) {
// 模拟处理请求
try {
Thread.sleep(200); // 请求处理时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread.sleep(100); // 每100ms发送一个请求
}
// 等待所有请求处理完成
service.shutdown();
service.awaitTermination(1, TimeUnit.MINUTES);
// 打印统计信息
limiter.printStats();
System.out.println("总运行时间: " + (System.currentTimeMillis() - startTime) + "ms");
limiter.shutdown();
}
}
总结
Java 提供的 CountDownLatch、CyclicBarrier 和 Semaphore 这三个同步工具类,为我们解决多线程协作问题提供了强大支持。下面的表格总结了它们的主要特点和使用场景:
同步工具类 | 核心功能 | 主要 API | 典型使用场景 | 使用注意事项 |
---|---|---|---|---|
CountDownLatch | 让一个或多个线程等待其他线程完成 | countDown() await() | 主线程等待子任务完成 服务启动依赖 多任务结果汇总 | 计数器不能重置 计数必须准确 防止永久等待 |
CyclicBarrier | 让一组线程互相等待,到达同步点后一起继续 | await() reset() | 多阶段计算 并行迭代算法 分治算法同步 | 可重复使用 处理屏障破坏 线程数必须匹配 |
Semaphore | 控制并发访问资源的线程数 | acquire() release() tryAcquire() | 连接池管理 流量控制 资源访问限制 | finally 中释放许可 匹配获取释放数量 避免许可泄漏 |
使用这些工具时,关键在于理解各自的应用场景和特点:
- 当你需要等待一组线程都完成工作,选择 CountDownLatch
- 当你需要一组线程相互等待并分阶段执行,选择 CyclicBarrier
- 当你需要控制并发访问资源的数量,选择 Semaphore
理解这些同步工具,不仅能帮助你编写更加高效和可靠的多线程程序,还能进一步提升你对 Java 并发编程的理解。
感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!
如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。