Java有界阻塞队列实现疑问:为何无需额外互斥锁?
关于LeetCode 1188用Semaphore实现有界阻塞队列的线程安全问题
你提到的这种仅用empty和full两个信号量、不加互斥锁直接用非线程安全的ArrayDeque的解法,实际上存在线程安全隐患,并不是严格正确的实现,只是可能通过了LeetCode的基础测试用例。
问题核心
ArrayDeque本身不是线程安全的,它的addLast、removeFirst等方法没有任何同步控制:
- 内部的
head和tail指针是普通int变量,不是volatile的,多线程下无法保证可见性; - 当多个线程同时执行
addLast时,可能出现tail指针更新丢失、数组元素被覆盖的情况,直接破坏队列结构; - 同理,多个线程同时执行
removeFirst也会导致head指针的混乱,引发元素丢失或重复取出的问题。
为什么你会觉得它“正确”?
LeetCode的测试用例可能只覆盖了阻塞逻辑验证:比如队列满时enqueue会阻塞,队列空时dequeue会阻塞,但没有构造真正的高并发修改场景(比如多个线程同时执行enqueue或dequeue),所以这种有隐患的解法能通过测试。
正确的Semaphore实现方式
要让基于ArrayDeque的实现真正线程安全,必须额外加一个互斥信号量(比如mutex,初始许可数为1),用来保护队列的所有修改和查询操作:
import java.util.ArrayDeque; import java.util.Deque; import java.util.concurrent.Semaphore; public class BoundedBlockingQueue { private final Semaphore empty; private final Semaphore full; private final Semaphore mutex; private final Deque<Integer> queue; public BoundedBlockingQueue(int capacity) { // 控制可用空位数量,初始为队列容量 empty = new Semaphore(capacity); // 控制可用元素数量,初始为0 full = new Semaphore(0); // 保证同一时刻只有一个线程能修改队列 mutex = new Semaphore(1); queue = new ArrayDeque<>(); } public void enqueue(int element) throws InterruptedException { empty.acquire(); // 等待直到有空位 mutex.acquire(); // 独占队列的修改权限 queue.addLast(element); mutex.release(); full.release(); // 增加一个可用元素 } public int dequeue() throws InterruptedException { full.acquire(); // 等待直到有元素 mutex.acquire(); int element = queue.removeFirst(); mutex.release(); empty.release(); // 增加一个可用空位 return element; } public int size() { int size = 0; try { mutex.acquire(); size = queue.size(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { mutex.release(); } return size; } }
这里的mutex信号量保证了所有对ArrayDeque的操作都是互斥的,结合empty和full的边界控制,才是符合线程安全要求的实现。
内容的提问来源于stack exchange,提问作者curiousengineer
相关产品推荐
相关产品推荐

