在多线程编程中,线程间的协调是一个核心问题。如何让多个线程按照特定规则执行?如何等待多个线程都完成任务后再进行下一步?如何控制同时访问资源的线程数量?JDK 中为我们提供了三个强大的同步工具类来解决这些问题:CountDownLatch、CyclicBarrier 和 Semaphore。

这篇文章将通过实际案例和图解,带你深入理解这三个工具类的使用方法和应用场景,让你的多线程编程更加得心应手。

1. CountDownLatch:等待多任务完成的倒计时门闩

1.1 什么是 CountDownLatch?

CountDownLatch(倒计时门闩)是一种同步辅助工具,它允许一个或多个线程等待,直到其他线程完成一组操作后才继续执行。

1.2 CountDownLatch 核心 API

  • CountDownLatch(int count):构造方法,初始化计数器值
  • await():等待计数器归零,会阻塞当前线程
  • await(long timeout, TimeUnit unit):带超时的等待
  • countDown():计数器减 1
  • getCount():获取当前计数器的值

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
开始处理整合的数据...

这个例子中:

  1. 创建了一个计数值为 3 的 CountDownLatch
  2. 启动 3 个线程分别从不同数据源加载数据
  3. 主线程调用latch.await()等待
  4. 每个工作线程完成数据加载后调用countDown()
  5. 当计数器归零后,主线程从 await()方法返回,继续执行后续逻辑

1.4 CountDownLatch 原理解析

CountDownLatch 内部基于 AQS(AbstractQueuedSynchronizer)实现,使用 AQS 的共享模式。当创建一个 CountDownLatch 实例时,会传入一个初始计数值,这个值会被保存在 AQS 的 state 变量中:

  • await()方法会先检查 state 是否为 0,如果是则继续执行,否则将当前线程加入等待队列
  • countDown()方法会将 state 值减 1,当减到 0 时,会唤醒所有在 await()上等待的线程
  • CountDownLatch 使用 AQS 的共享模式,允许多个线程同时获取同步状态

1.5 CountDownLatch 的注意事项

  1. 计数器不能重置:CountDownLatch 的计数器无法重置,一旦计数到 0,就不能再用了
  2. 只能等待一次性事件:适合等待一次性事件,不适合周期性重复的场景
  3. countDown()可以在任何线程中调用:不一定要在执行任务的线程中调用
  4. 注意处理中断异常:await()方法会抛出 InterruptedException
  5. 可能导致永久等待:如果某个任务没有正确调用 countDown(),可能导致等待线程永远阻塞
  6. 避免 countDown()调用次数超过初始计数:虽然这不会导致异常,但它表明程序逻辑可能存在问题

2. CyclicBarrier:可重用的线程同步屏障

2.1 什么是 CyclicBarrier?

CyclicBarrier(循环屏障)是一种同步辅助工具,它允许多个线程在某个点上互相等待,当所有线程都到达这个点后,屏障打开,所有线程继续执行。与 CountDownLatch 不同,CyclicBarrier 可以重复使用。

2.2 CyclicBarrier 核心 API

  • CyclicBarrier(int parties):创建一个屏障,等待指定数量的线程
  • CyclicBarrier(int parties, Runnable barrierAction):创建一个屏障,并在所有线程到达时执行 barrierAction
  • await():等待所有线程到达屏障点
  • 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所有任务完成!

在这个例子中:

  1. 创建了一个 parties 为 3 的 CyclicBarrier,并指定了屏障动作
  2. 三个工作线程分别执行三个阶段的任务
  3. 每完成一个阶段,线程就会调用barrier.await()等待其他线程
  4. 当所有线程都到达屏障点,最后到达的线程会执行屏障动作,然后所有线程被唤醒并继续下一阶段
  5. 整个过程会重复直到所有阶段完成

2.4 CyclicBarrier 原理解析

CyclicBarrier 也是基于 AQS 实现的,但它使用的是 ReentrantLock 和 Condition,这与 CountDownLatch 的直接使用 AQS 不同:

  • 内部使用ReentrantLockCondition实现线程同步
  • 维护计数器,记录还未到达屏障的线程数
  • 当线程调用await()时,计数器减 1
  • 如果计数器不为 0,当前线程进入等待状态
  • 当最后一个线程到达屏障点,计数器变为 0

    • 执行屏障动作(如果有)
    • 重置计数器为初始值
    • 唤醒所有等待的线程

2.5 CyclicBarrier 的注意事项

  1. 可以重复使用:与 CountDownLatch 不同,CyclicBarrier 可以通过自动重置或手动调用 reset()方法重置
  2. 必须所有线程都调用 await():如果有线程没有调用 await(),可能导致其他线程永久等待
  3. 处理中断和超时情况:await()方法会抛出 InterruptedException 和 BrokenBarrierException
  4. 注意屏障破坏:当有线程中断或超时,屏障会被破坏(broken),需要调用 reset()重置
  5. parties 值必须与参与线程数匹配:否则可能导致不必要的等待或永久阻塞

3. Semaphore:控制并发访问的信号量

3.1 什么是 Semaphore?

Semaphore(信号量)是一种计数信号量,用于控制同时访问特定资源的线程数量。它维护了一组许可证,线程在访问资源前必须获取许可,用完后释放。

graph TD
    A[线程池] --> B[多个线程]
    B --> C[Semaphore<br>permits=3]
    C --> D["acquire()"]
    D --> E[获取许可]
    E --> F[访问受限资源]
    F --> G["release()"]
    G --> H[释放许可]
    H --> C

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
...

在这个例子中:

  1. 创建了一个 permits 为 10 的 Semaphore,控制同时活动的连接数上限
  2. 每次获取连接前,必须先获取一个许可
  3. 使用完连接后,释放许可
  4. 当所有许可都被获取,新的请求会被阻塞,直到有许可被释放
  5. 连接池复用已创建的连接,提高效率

3.4 Semaphore 原理解析

Semaphore 也是基于 AQS 实现的,支持公平和非公平两种模式,其工作原理如下:

  • acquire()方法会尝试将 AQS 的 state 值减 1,如果 state 大于 0 则成功获取许可,否则线程进入等待队列
  • release(n)方法会将 state 值加 n,并尝试唤醒等待队列中的线程
  • 公平模式下,线程按照 FIFO 顺序获取许可
  • 非公平模式下,新到的线程可以"插队"获取许可

3.5 Semaphore 的注意事项

  1. 始终在 finally 块中释放许可:防止程序异常导致许可泄漏,许可泄漏会导致资源耗尽
  2. 注意公平性选择:非公平模式性能更好,但可能导致某些线程长时间等待
  3. 可以释放"非自己获取"的许可:任何线程都可以调用 release(),即使它没有调用过 acquire()
  4. 注意许可使用数量一致:如果 acquire(n),应该对应 release(n)
  5. 避免信号量值过大:信号量值过大可能导致资源过度使用,失去控制意义

4. 三大同步工具类的对比与选择

让我们对这三个同步工具类进行对比,以便在不同场景下做出正确选择:

4.1 功能对比

特性CountDownLatchCyclicBarrierSemaphore
主要功能等待其他线程完成线程之间相互等待控制并发访问量
计数方向递减到 0(一次性)递减到 0 后重置(循环)动态增减(许可)
重置能力不可重置可以重置可动态调整许可
线程等待单方向等待(一方等多方)多方互相等待竞争许可的阻塞
典型使用场景主线程等待多个子任务多线程分阶段执行有限资源访问控制
屏障动作所有线程到达时执行
AQS 实现方式直接使用 AQS 共享模式基于 ReentrantLock 和 Condition使用 AQS 共享模式

4.2 应用场景对比

  1. CountDownLatch 适用场景

    • 主线程需要等待多个工作线程完成初始化
    • 等待多个服务启动完成后再对外提供服务
    • 异步任务的结果统一处理
    • 不适用场景:需要重复使用同一个同步点;需要执行复杂的屏障动作
  2. CyclicBarrier 适用场景

    • 多阶段计算任务,每个阶段都需要所有线程完成
    • 并行迭代算法中的同步点
    • 多个线程需要相互等待的场景
    • 不适用场景:单向等待(一个线程等待多个线程);不确定参与线程数量的场景
  3. 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 中释放许可
匹配获取释放数量
避免许可泄漏

使用这些工具时,关键在于理解各自的应用场景和特点:

  1. 当你需要等待一组线程都完成工作,选择 CountDownLatch
  2. 当你需要一组线程相互等待并分阶段执行,选择 CyclicBarrier
  3. 当你需要控制并发访问资源的数量,选择 Semaphore

理解这些同步工具,不仅能帮助你编写更加高效和可靠的多线程程序,还能进一步提升你对 Java 并发编程的理解。


感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!

如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~


异常君
1 声望1 粉丝

在 Java 的世界里,永远有下一座技术高峰等着你。我愿做你登山路上的同频伙伴,陪你从看懂代码到写出让自己骄傲的代码。咱们,代码里见!