在多线程开发中,死锁、资源竞争、线程协调等问题如同暗礁,稍有不慎就会导致程序崩溃。而JUC同步工具类正是解决这些问题的瑞士军刀!
一、同步工具类核心价值:线程协作的艺术
在高并发系统中,线程协作是保证数据一致性和系统稳定性的关键。Java通过内置锁synchronized
和wait/notify
机制提供了基础同步能力,但在复杂场景下存在诸多限制:
- 粒度控制不足 - 无法细粒度控制并发访问
- 灵活性欠缺 - 难以实现多种协作模式
- 编码复杂 - 需要手动维护等待队列
JUC同步工具类正是为了弥补这些缺陷而生,基于AQS(AbstractQueuedSynchronizer)构建,提供更丰富的线程协作能力。
二、五大核心同步工具详解(附源码级原理)
1. 倒计时门栓:CountDownLatch
场景痛点:主线程需要等待所有子任务完成才能继续执行
解决方案:
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 创建计数器为3的门栓CountDownLatch latch = new CountDownLatch(3); // 创建3个工作线程for (int i = 0; i < 3; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + " 开始执行");try {// 模拟业务处理Thread.sleep(new Random().nextInt(5000)); } catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + " 执行完成");latch.countDown(); // 计数减1}, "工作线程-"+i).start();}System.out.println("主线程等待子任务完成...");latch.await(); // 阻塞直到计数器归零System.out.println("所有子任务已完成,主线程继续执行");}
}
源码级原理:
- 内部维护计数器
state
countDown()
调用releaseShared(1)
减少计数- 当
state==0
时,通过unpark()
唤醒阻塞在await()
上的线程
适用场景:
- 启动服务等待依赖组件初始化完成
- MapReduce计算任务等待所有Map任务完成
- 测试用例并发执行后的结果汇总
2. 循环屏障:CyclicBarrier
场景痛点:多个线程需要相互等待,达到共同起点后再并行执行
public class CyclicBarrierDemo {public static void main(String[] args) {// 创建屏障点为3的循环屏障CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程已达屏障点,开始下一阶段"));for (int i = 0; i < 3; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 准备阶段一");Thread.sleep(1000);barrier.await(); // 等待其他线程System.out.println(Thread.currentThread().getName() + " 准备阶段二");Thread.sleep(1500);barrier.await();System.out.println(Thread.currentThread().getName() + " 完成所有任务");} catch (Exception e) {e.printStackTrace();}}, "计算线程-"+i).start();}}
}
核心特点:
- 自动计数器重置,可重复使用
- 支持
Runnable
回调(所有线程到达后执行) - 提供
reset()
方法强制重置状态
源码原理:
- 基于ReentrantLock和Condition实现等待队列
- 通过
dowait()
管理线程阻塞和唤醒 - 每代(Generation)维护独立的计数状态
适用场景:
- 多阶段数据处理(如ETL中的清洗->转换->加载)
- 遗传算法中的多代计算
- 并发测试中的同步点控制
3. 信号量:Semaphore(并发的流量阀门)
场景痛点:需要控制对共享资源的并发访问数量
public class SemaphoreDemo {// 模拟数据库连接池(最大5个连接)private static final Semaphore CONNECTION_POOL = new Semaphore(5, true); // 公平模式public static void main(String[] args) {// 模拟10个并发请求for (int i = 0; i < 10; i++) {new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " 尝试获取连接");// 获取许可(支持超时设置tryAcquire)CONNECTION_POOL.acquire(); System.out.println(Thread.currentThread().getName() + " 获取连接成功");// 模拟SQL查询耗时Thread.sleep(2000); } catch (InterruptedException e) {e.printStackTrace();} finally {// 保证释放CONNECTION_POOL.release(); System.out.println(Thread.currentThread().getName() + " 释放连接");}}, "请求线程-"+i).start();}}
}
高级用法:
// 一次性获取多个许可
semaphore.acquire(2); // 尝试获取许可(非阻塞)
if(semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {// ...
}// 可中断获取
semaphore.acquireInterruptibly();
源码原理:
- 基于AQS实现共享模式
acquire()
尝试获取许可,不足时线程入队等待release()
释放许可并唤醒等待线程- 公平/非公平模式通过AQS队列实现
适用场景:
- 数据库连接池限流
- API接口限流(如每秒100次调用)
- 操作系统级别的资源限制
4. 数据交换器:Exchanger(线程间点对点数据传输)
场景痛点:两个线程需要安全交换数据
public class ExchangerDemo {public static void main(String[] args) {Exchanger<String> exchanger = new Exchanger<>();// 生产者线程new Thread(() -> {try {String data = "商品数据包";System.out.println("生产者发送: " + data);// 阻塞等待消费者String received = exchanger.exchange(data); System.out.println("生产者接收: " + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();// 消费者线程new Thread(() -> {try {Thread.sleep(1000); // 模拟处理延时String data = "确认回执";System.out.println("消费者发送: " + data);// 与生产者交换数据String received = exchanger.exchange(data); System.out.println("消费者接收: " + received);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}).start();}
}
原理深潜:
- 使用
ThreadLocal
存储每个线程的Node - 通过CAS操作进行节点匹配
- 支持超时版本的交换方法
5. 分阶段协调器:Phaser(灵活版CyclicBarrier)
场景痛点:需要动态调整参与者数量的多阶段任务
public class PhaserDemo {public static void main(String[] args) {// 创建分阶段协调器Phaser phaser = new Phaser(1); // 主线程也注册for (int i = 0; i < 3; i++) {// 动态注册新参与者phaser.register(); new Thread(new Task(phaser), "任务线程-"+i).start();}// 第一阶段:准备资源System.out.println("主线程准备资源...");phaser.arriveAndAwaitAdvance(); // 等待所有线程到达System.out.println("所有资源准备完成,开始处理");// 第二阶段:处理数据phaser.arriveAndAwaitAdvance();System.out.println("数据处理完成,进入收尾阶段");// 主线程退出协调phaser.arriveAndDeregister(); }static class Task implements Runnable {private final Phaser phaser;Task(Phaser phaser) {this.phaser = phaser;}@Overridepublic void run() {// 阶段1:准备资源prepareResource();phaser.arriveAndAwaitAdvance();// 阶段2:处理数据processData();phaser.arriveAndAwaitAdvance();// 任务完成,注销phaser.arriveAndDeregister();}private void prepareResource() {// ...}private void processData() {// ...}}
}
核心优势:
- 动态注册:随时增减参与者(
register()/deregister()
) - 分层结构:支持树形结构降低竞争
- 阶段控制:通过
onAdvance()
自定义栅栏动作
三、同步工具类对比决策表
工具类 | 协作模型 | 重用性 | 参与者 | 最佳适用场景 |
---|---|---|---|---|
CountDownLatch | 1等N | ❌ 一次性 | 固定 | 主任务等待子任务完成 |
CyclicBarrier | N等N | ✅ 可循环 | 固定 | 多阶段并行计算 |
Semaphore | 资源控制 | ✅ 不限次 | 动态 | 资源池限流 |
Exchanger | 点对点 | ✅ 可循环 | 2个 | 线程间安全数据交换 |
Phaser | 分层协调 | ✅ 可循环 | 动态 | 分阶段动态扩容/缩容任务 |
四、避坑指南:五大高危陷阱
-
CountDownLatch死锁陷阱
// 错误示范:计数设置>实际线程数 CountDownLatch latch = new CountDownLatch(5); // 只有3个线程调用了countDown()
解决方案:确保计数与实际任务数严格一致
-
CyclicBarrier嵌套调用崩溃
// 同一线程内多次await() barrier.await(); barrier.await(); // 可能引发BrokenBarrierException
修复方案:确保每个线程每代只调用一次
await()
-
Semaphore忘记释放
try {semaphore.acquire();// 发生异常! } catch(Exception e) {// 未调用release()导致许可丢失 }
规范写法:始终在finally块中释放
try {semaphore.acquire();// ... } finally {semaphore.release(); }
-
Phaser动态注销导致丢失
// 错误:在未到达时就注销 phaser.arrive(); phaser.deregister(); // 其他线程可能仍在等待
正确方式:使用
arriveAndDeregister()
原子操作 -
Exchanger线程不匹配
// 线程1 exchanger.exchange(data1); // 线程2未调用exchange()导致线程1永久阻塞
解决思路:设置超时版本
exchanger.exchange(data, 1, TimeUnit.SECONDS);
五、性能优化黄金法则
- 减少锁竞争:使用Phaser替代CyclicBarrier处理大规模线程
- 避免嵌套阻塞:在Semaphore保护的代码块中不要调用await()
- 超时设置:所有阻塞操作都使用带超时版本
- 层级化设计:
- 状态监控:通过
getWaitingCount()
等方法实时监控同步器状态
六、实战场景最佳组合
-
高并发订单处理系统
-
实时数据计算管道
[数据采集] -> (Exchanger交换数据) -> [清洗线程] -> (CyclicBarrier阶段同步) -> [计算线程] -> (Phaser动态扩展) -> [存储线程]
结语:选择比努力更重要
JUC同步工具类提供了多线程协作的多种武器,理解它们的实现机制和适用场景,就像将军了解自己的兵器库。记住:
- 简单协作:CountDownLatch
- 多阶段并行:CyclicBarrier/Phaser
- 流量控制:Semaphore
- 数据传输:Exchanger
合理选择同步工具,能极大提升系统稳定性和开发效率,让你的并发程序如虎添翼!
技术总是在迭代更新,但并发编程的核心思想永不落伍——高效协同、有序共享、平衡竞争!