Java 常用并发工具类:从原理到实战

Java 并发工具类(JUC)提供了开箱即用的并发控制能力,避免我们重复造轮子。你提到的 CountDownLatchCyclicBarrierSemaphoreExchanger 是最核心的四个,下面我会从核心作用、底层原理、使用场景、代码示例四个维度,把每个工具类讲清楚,方便你理解和使用。

一、CountDownLatch:倒计时门闩(一次性)

核心作用

一个或多个线程等待其他线程完成一组操作后,再继续执行(比如主线程等待所有子线程初始化完成)。

  • 核心特性:一次性,计数器减到 0 后,无法重置,只能新建对象。

底层原理

基于 AQS(AbstractQueuedSynchronizer)实现:

  • CountDownLatch 内部维护一个计数器,初始化时设置为指定值;
  • 调用 countDown() 时,计数器减 1(AQS 释放共享锁);
  • 调用 await() 时,线程阻塞,直到计数器变为 0(AQS 获取共享锁成功)。

典型场景

  1. 主线程等待多个子线程完成初始化/任务执行;
  2. 并发测试:等待所有测试线程启动后,再统一开始计时。

代码示例

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() 时,进入等待状态,直到等待线程数等于栅栏数;
  • 所有线程到达后,执行屏障动作(如果有),然后重置计数器,开启下一轮。

典型场景

  1. 多线程分阶段任务:比如“数据加载→数据计算→数据汇总”,每个阶段所有线程完成后进入下阶段;
  2. 模拟并发:让多个线程同时开始执行。

代码示例

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 释放共享锁),唤醒等待的线程。

典型场景

  1. 限流:比如限制同时访问数据库连接池的线程数;
  2. 资源池管理:比如控制同时使用的线程池/连接池数量;
  3. 模拟并发:控制同时执行的线程数。

代码示例

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,两者同时返回。

典型场景

  1. 数据校对:比如两个线程分别计算同一批数据的结果,交换后校对;
  2. 生产者-消费者:简单的一对一数据交换(替代队列)。

代码示例

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 节点

总结

  1. CountDownLatch:一次性等待,适合“主线程等多线程完成”的场景;
  2. CyclicBarrier:可循环的互相等待,适合“多线程分阶段协作”的场景;
  3. Semaphore:许可证限流,适合“控制资源并发访问数”的场景;
  4. Exchanger:一对一数据交换,适合“两个线程数据校对/简单通信”的场景。

这些工具类都是 JUC 包的核心,底层大多基于 AQS 或锁机制实现,掌握它们可以避免手写复杂的同步逻辑,提升并发代码的可读性和稳定性。

posted @ 2026-03-07 21:26  七星6609  阅读(0)  评论(0)    收藏  举报