Java高并发同步构造实战:ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier深度解析

1. 这不是“理论课”,是我在高并发服务里踩出的血路

“深入浅出多线程系列之五:一些同步构造(上篇)”——看到这个标题,别急着划走。它不是教科书里那种堆砌 volatile synchronized 语法糖的填鸭式讲解,而是我过去八年在电商秒杀系统、金融实时风控引擎、IoT设备管理平台这三类典型高并发场景中,亲手用 ReentrantLock CountDownLatch CyclicBarrier Semaphore 反复试错、压测、回滚、再上线后沉淀下来的实操手册。标题里那个“上篇”很关键:我们今天只聊 显式锁与协调型同步器 ,不碰 Future CompletableFuture 这些异步编排工具,更不提前进入 Phaser StampedLock 这种进阶选手——那些留到下篇,因为90%的线上事故,根源就藏在今天要讲的这四个构造里。

我见过太多人把 CountDownLatch 当“等所有线程结束”的万能胶水,结果在订单超时关单服务里,因一个子任务卡死导致整个关单流程挂起30分钟;也见过团队为提升吞吐量盲目把 synchronized 块替换成 ReentrantLock ,却忘了配置公平性参数,在促销高峰时引发线程饥饿,订单创建成功率直接掉到62%。这些都不是概念错误,而是对同步构造 行为边界、资源语义、失败传播机制 缺乏肌肉记忆级的理解。所以这篇内容的核心价值很直白:帮你建立一套“看到代码就能预判线程行为”的直觉——比如读到 new Semaphore(5) ,你脑子里立刻浮现出“最多5个线程能同时穿过这个门禁”,而不是先去翻JDK文档;看到 latch.await(10, TimeUnit.SECONDS) ,你条件反射想到“这里必须有且仅有count个线程调用countDown(),否则10秒后所有等待线程会带着TimeoutException醒来,且latch状态永久失效”。

适合谁?如果你正在写需要处理并行任务的业务代码(比如批量导入用户数据、聚合多个API结果、分片计算报表),或者负责维护一个QPS过万的服务,又或者刚被面试官问倒“ ReentrantLock synchronized 多了什么”,那这篇就是为你写的。不需要你背源码,但要求你愿意跟着我一起,在脑中模拟线程调度的每一步——毕竟,多线程的真相从来不在文档里,而在CPU调度器切换线程的那几纳秒间隙中。

2. 同步构造的本质:不是“加锁”,而是“协商资源使用权”

2.1 所有同步构造都服务于一个底层契约

很多人一提同步就想到“锁住资源”,这是巨大误区。真正决定程序正确性的,从来不是“谁占着资源”,而是“谁有资格申请资源”。 ReentrantLock CountDownLatch CyclicBarrier Semaphore 这四个构造,表面形态差异极大,但内核共享同一套设计哲学: 它们都是线程间达成资源使用协议的协商工具,而非资源本身的看门人

举个生活化例子:想象一栋写字楼的会议室预订系统。 ReentrantLock 像一把实体钥匙——只有拿到钥匙的人才能进会议室,用完必须归还; Semaphore 则像会议室门禁卡的发放数量限制——系统总共发5张卡,同时最多5人能进门,但没人管你进去后干啥; CountDownLatch 好比会议签到台——主持人设了10个签到点,必须等10个人全部签完,会议才开始,签到点本身不提供座位; CyclicBarrier 则是电梯轿厢——所有人必须等齐8个人才关门上升,少一个都不动,而且电梯到了楼层还能重复使用。

这个比喻的关键在于: 所有工具都不存储业务数据,也不执行业务逻辑,它们只负责在特定条件下阻塞或唤醒线程 。当你在代码里写 lock.lock() ,你不是在“锁住某个变量”,而是在向JVM申请一个许可;当 latch.await() 返回,你得到的不是数据,而是一个“现在可以安全执行后续逻辑”的信号。这种认知转变至关重要——它让你从“怎么防止数据被改乱”的被动防御,转向“如何让线程按预期节奏协作”的主动设计。

提示:JDK中所有同步构造的实现都基于 AbstractQueuedSynchronizer (AQS)框架。AQS本质是个双向链表队列+一个int类型的state变量。 state 代表资源的抽象状态(如锁的持有次数、信号量的剩余许可数、倒计时的剩余数值),而队列则管理所有因申请资源失败而被挂起的线程。理解这点,你就明白为什么 ReentrantLock 能支持可重入、公平/非公平模式,而 Semaphore 能精确控制并发数——它们只是对 state 和队列操作策略的不同封装。

2.2 为什么必须区分“独占”与“共享”语义

AQS将同步状态分为两种模式: EXCLUSIVE (独占)和 SHARED (共享)。这个区分直接决定了构造器的行为天花板:

  • ReentrantLock 是典型的 EXCLUSIVE 模式:同一时刻,只有一个线程能成功获取state(即获得锁)。它的 state 值代表重入次数,每次 lock() 加1, unlock() 减1,为0时释放所有权。
  • Semaphore CountDownLatch CyclicBarrier 则属于 SHARED 模式:多个线程可同时获取state,只要state值允许。 Semaphore state 是剩余许可数, CountDownLatch state 是剩余倒计数值, CyclicBarrier 内部用 ReentrantLock + Condition 组合实现,其“等待线程数”由另一个state变量维护。

这个区别带来一个致命实践陷阱: 永远不要试图用 ReentrantLock 实现类似 Semaphore 的限流功能 。我曾见过有同事为控制数据库连接池使用量,写这样的代码:

// ❌ 危险示范:用ReentrantLock模拟限流
private final ReentrantLock lock = new ReentrantLock();
private int permits = 5;

public void acquire() throws InterruptedException {
    while (permits <= 0) {
        Thread.sleep(10); // 错误!忙等待消耗CPU
    }
    lock.lock(); // 实际只锁住一个线程,无法控制并发数
    permits--;
}

这段代码问题重重: while 循环是忙等待, lock.lock() 只保证 permits-- 原子性,但完全无法阻止第6个线程在第1个线程 permits-- 前就进入循环。正确解法是直接用 Semaphore

// ✅ 正确:用原生Semaphore
private final Semaphore semaphore = new Semaphore(5);

public void acquire() throws InterruptedException {
    semaphore.acquire(); // 阻塞直到获得许可
}

Semaphore acquire() 方法会原子性地将 state 减1,若减后为负,则将当前线程加入AQS队列挂起; release() 则原子性加1,并唤醒队列头节点。这种基于AQS的原子操作,才是高并发下可靠的资源协调基础。

2.3 “可重入”不是特性,而是避免死锁的生存法则

ReentrantLock 的“可重入”常被当作高级功能宣传,但在真实系统中,它是防止死锁的刚需。考虑一个电商库存扣减服务:

public class InventoryService {
    private final ReentrantLock lock = new ReentrantLock();

    public void deductStock(Long skuId, int quantity) {
        lock.lock();
        try {
            // 检查库存
            if (getStock(skuId) < quantity) {
                throw new InsufficientStockException();
            }
            // 扣减库存
            updateStock(skuId, -quantity);
            // 发送扣减成功事件(可能触发其他服务调用)
            eventPublisher.publish(new StockDeductedEvent(skuId, quantity));
        } finally {
            lock.unlock();
        }
    }

    // 事件处理器中可能再次调用本类方法
    public void handleOrderCreatedEvent(OrderCreatedEvent event) {
        // ... 处理逻辑
       
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值