You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java并发:环形缓冲区两种实现中额外同步块的作用疑问

环形缓冲区双锁实现中跨锁唤醒的核心作用

首先得明确Java里wait()/notifyAll()的硬性规则:调用某个对象的wait/notify方法,必须先持有该对象的锁;而且调用wait的线程会进入该对象的等待队列,只有持有同一对象锁的线程调用notifyAll,才能唤醒这个队列里的线程。

回到你的双锁环形缓冲区场景:

  • 当缓冲区满时,添加线程会持有addLock并调用addLock.wait(),进入addLock的等待队列
  • 当缓冲区空时,移除线程会持有removeLock并调用removeLock.wait(),进入removeLock的等待队列

现在拆解两个额外同步块的作用:

  1. add方法里的synchronized(removeLock)同步块
    当add线程成功添加元素后,缓冲区从空转为非空——此时可能有一批移除线程因为之前缓冲区空,正卡在removeLock的等待队列里。要唤醒这些线程,必须调用removeLock.notifyAll(),但根据规则,调用这个方法必须先持有removeLock的锁。这个同步块的唯一作用就是临时获取removeLock的锁,合法地唤醒等待移除的线程。

  2. remove方法里的synchronized(addLock)同步块
    同理,当remove线程成功移除元素后,缓冲区从满转为非满——此时可能有添加线程因为之前缓冲区满,正卡在addLock的等待队列里。要唤醒这些线程,必须持有addLock的锁才能调用addLock.notifyAll(),这个同步块就是用来获取锁并执行唤醒操作的。

为什么不能用当前持有的锁来唤醒?因为等待的线程不在当前锁的队列里。比如add线程持有的addLock,它的等待队列里是等待添加的线程,而我们需要唤醒的是等待移除的线程——这些线程在removeLock的队列里,必须用removeLock来触发唤醒。

对比单锁(同步方法)的实现:所有线程都挤在同一个锁的队列里,notifyAll会唤醒所有等待线程,不仅会出现不必要的无效唤醒(比如唤醒了等待添加的线程但缓冲区还是满的),还会导致同一时间只能有一个线程操作,并发度极低。双锁方案的跨锁唤醒,既保证了添加和移除操作可以并行执行,又能精准唤醒真正需要被唤醒的线程,大幅提升了并发效率。

内容的提问来源于stack exchange,提问作者FaheDevvs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 06:57:08