Java常用并发工具类原理与实战如何深入理解?

摘要:Java 并发工具类(JUC)提供了开箱即用的并发控制能力,避免我们重复造轮子。你提到的 CountDownLatch、CyclicBarrier、Semaphore、Exchanger 是最核心的四个,下面我会从核心作用、底层原理、使用场
Java 并发工具类(JUC)提供了开箱即用的并发控制能力,避免我们重复造轮子。你提到的 CountDownLatch、CyclicBarrier、Semaphore、Exchanger 是最核心的四个,下面我会从核心作用、底层原理、使用场景、代码示例四个维度,把每个工具类讲清楚,方便你理解和使用。 一、CountDownLatch:倒计时门闩(一次性) 核心作用 让一个或多个线程等待其他线程完成一组操作后,再继续执行(比如主线程等待所有子线程初始化完成)。 核心特性:一次性,计数器减到 0 后,无法重置,只能新建对象。 底层原理 基于 AQS(AbstractQueuedSynchronizer)实现: CountDownLatch 内部维护一个计数器,初始化时设置为指定值; 调用 countDown() 时,计数器减 1(AQS 释放共享锁); 调用 await() 时,线程阻塞,直到计数器变为 0(AQS 获取共享锁成功)。 典型场景 主线程等待多个子线程完成初始化/任务执行; 并发测试:等待所有测试线程启动后,再统一开始计时。 代码示例 import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { int threadNum = 3; // 初始化计数器为3 CountDownLatch latch = new CountDownLatch(threadNum); for (int i = 1; i <= threadNum; i++) { int taskId = i; new Thread(() -> { try { System.out.println("任务" + taskId + "开始执行"); Thread.sleep(1000); // 模拟任务执行 System.out.println("任务" + taskId + "执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 计数器减1 latch.countDown(); } }).start(); } // 主线程等待所有子线程完成 latch.await(); System.out.println("所有任务执行完毕,主线程继续执行"); } } 执行结果: 任务1开始执行 任务2开始执行 任务3开始执行 任务1执行完成 任务2执行完成 任务3执行完成 所有任务执行完毕,主线程继续执行 二、CyclicBarrier:循环栅栏(可重复) 核心作用 让一组线程互相等待,直到所有线程都到达某个“栅栏点”,然后所有线程同时继续执行(比如多线程计算后,统一汇总结果)。 核心特性:可循环使用,计数器重置后可再次使用;支持设置「屏障动作」,所有线程到达后执行。 底层原理 基于 ReentrantLock + Condition 实现(而非直接用 AQS): 内部维护「等待线程数」和「栅栏数」(每次到达栅栏点的线程数); 线程调用 await() 时,进入等待状态,直到等待线程数等于栅栏数; 所有线程到达后,执行屏障动作(如果有),然后重置计数器,开启下一轮。 典型场景 多线程分阶段任务:比如“数据加载→数据计算→数据汇总”,每个阶段所有线程完成后进入下阶段; 模拟并发:让多个线程同时开始执行。 代码示例 import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { int threadNum = 3; // 初始化栅栏:3个线程到达后,执行屏障动作(汇总线程) CyclicBarrier barrier = new CyclicBarrier(threadNum, () -> { System.out.println("所有线程到达栅栏点,执行汇总操作"); }); for (int i = 1; i <= threadNum; i++) { int taskId = i; new Thread(() -> { try { System.out.println("线程" + taskId + "执行阶段1任务"); Thread.sleep(1000); System.out.println("线程" + taskId + "到达栅栏点"); // 等待其他线程到达 barrier.await(); // 所有线程到达后,执行阶段2任务 System.out.println("线程" + taskId + "执行阶段2任务"); } catch (Exception e) { e.printStackTrace(); } }).start(); } } } 执行结果: 线程1执行阶段1任务 线程2执行阶段1任务 线程3执行阶段1任务 线程1到达栅栏点 线程2到达栅栏点 线程3到达栅栏点 所有线程到达栅栏点,执行汇总操作 线程3执行阶段2任务 线程1执行阶段2任务 线程2执行阶段2任务 三、Semaphore:信号量(限流/资源控制) 核心作用 控制同时访问某个资源的线程数(限流),通过“许可证”机制实现: 核心特性:支持「公平/非公平」获取许可证;可动态调整许可证数量(release(n))。 底层原理 基于 AQS 实现: 初始化时设置许可证数量(AQS 的 state 变量); 调用 acquire() 时,线程尝试获取 1 个许可证(AQS 获取共享锁),无许可证则阻塞; 调用 release() 时,线程释放 1 个许可证(AQS 释放共享锁),唤醒等待的线程。 典型场景 限流:比如限制同时访问数据库连接池的线程数; 资源池管理:比如控制同时使用的线程池/连接池数量; 模拟并发:控制同时执行的线程数。 代码示例 import java.util.concurrent.Semaphore; public class SemaphoreDemo { public static void main(String[] args) { int permitNum = 2; // 最多2个线程同时执行 // 非公平锁(默认),如果要公平锁:new Semaphore(2, true) Semaphore semaphore = new Semaphore(permitNum); for (int i = 1; i <= 5; i++) { int taskId = i; new Thread(() -> { try { // 获取许可证 semaphore.acquire(); System.out.println("线程" + taskId + "获取许可证,开始执行"); Thread.sleep(1000); // 模拟任务执行 } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放许可证 semaphore.release(); System.out.println("线程" + taskId + "释放许可证"); } }).start(); } } } 执行结果(核心:同一时间只有2个线程执行): 线程1获取许可证,开始执行 线程2获取许可证,开始执行 线程1释放许可证 线程3获取许可证,开始执行 线程2释放许可证 线程4获取许可证,开始执行 线程3释放许可证 线程5获取许可证,开始执行 线程4释放许可证 线程5释放许可证 四、Exchanger:线程数据交换器 核心作用 让两个线程在指定点交换数据(一对一交换),如果只有一个线程到达,会阻塞直到另一个线程到达。 核心特性:仅支持两个线程交换;可设置超时时间(exchange(V x, long timeout, TimeUnit unit))。 底层原理 基于 CAS + Node 节点实现(存储等待的线程和交换的数据): 线程 A 调用 exchange() 时,创建 Node 节点并存储数据,然后自旋等待; 线程 B 调用 exchange() 时,找到线程 A 的 Node 节点,交换数据,唤醒线程 A,两者同时返回。 典型场景 数据校对:比如两个线程分别计算同一批数据的结果,交换后校对; 生产者-消费者:简单的一对一数据交换(替代队列)。 代码示例 import java.util.concurrent.Exchanger; public class ExchangerDemo { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); // 线程1:生产数据 new Thread(() -> { try { String data1 = "线程1的计算结果"; System.out.println("线程1准备交换数据:" + data1); // 等待线程2交换数据 String data2 = exchanger.exchange(data1); System.out.println("线程1收到交换数据:" + data2); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 线程2:生产数据 new Thread(() -> { try { String data2 = "线程2的计算结果"; System.out.println("线程2准备交换数据:" + data2); // 等待线程1交换数据 String data1 = exchanger.exchange(data2); System.out.println("线程2收到交换数据:" + data1); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } 执行结果: 线程1准备交换数据:线程1的计算结果 线程2准备交换数据:线程2的计算结果 线程1收到交换数据:线程2的计算结果 线程2收到交换数据:线程1的计算结果 补充:四大工具类核心区别 工具类 核心目标 特性 底层依赖 CountDownLatch 一个/多线程等多线程完成 一次性、不可重置 AQS(共享锁) CyclicBarrier 多线程互相等待到栅栏点 可循环、支持屏障动作 ReentrantLock+Condition Semaphore 控制并发访问线程数 公平/非公平、可扩容 AQS(共享锁) Exchanger 两个线程交换数据 一对一、可超时 CAS+Node 节点 总结 CountDownLatch:一次性等待,适合“主线程等多线程完成”的场景; CyclicBarrier:可循环的互相等待,适合“多线程分阶段协作”的场景; Semaphore:许可证限流,适合“控制资源并发访问数”的场景; Exchanger:一对一数据交换,适合“两个线程数据校对/简单通信”的场景。 这些工具类都是 JUC 包的核心,底层大多基于 AQS 或锁机制实现,掌握它们可以避免手写复杂的同步逻辑,提升并发代码的可读性和稳定性。