java中priorityqueue非线程安全,需用reentrantlock在每个方法中显式加锁(如offer、poll、peek等)并配合try-finally确保释放,不可仅声明锁字段;推荐封装为自定义类,或直接使用priorityblockingqueue。

Java 中的 PriorityQueue 本身不是线程安全的,不能直接在多线程环境中使用。若需在并发场景下保证其线程安全,常见做法是用 ReentrantLock 封装操作逻辑,而非简单地“包装 PriorityQueue 对象”。关键在于对所有访问(入队、出队、查看)加锁,确保同一时刻只有一个线程执行修改或读取操作。
为什么不能只给 PriorityQueue 加个 ReentrantLock 字段就完事
单纯在类中定义一个 ReentrantLock 和一个 PriorityQueue 字段,并不自动带来线程安全。必须显式地在每次调用 offer()、poll()、peek() 等方法时获取锁并释放锁。否则,多个线程仍可能同时进入临界区,导致数据错乱或 ConcurrentModificationException。
如何用 ReentrantLock 正确封装 PriorityQueue
推荐方式是封装成一个自定义容器类,将锁和队列作为私有成员,并在每个公共方法中统一加锁:
- 使用
lock.lock()和lock.unlock()配对(建议配合 try-finally,避免死锁) - 所有可能改变队列状态的操作(如
offer、poll)必须加锁 - 只读操作(如
peek、size、isEmpty)也应加锁——因为PriorityQueue的内部结构(数组堆)在并发读写下可能处于中间状态 - 不要在锁内做耗时操作(如 I/O、远程调用),否则会严重降低吞吐量
一个典型的安全封装示例
以下是一个最小可行的线程安全优先队列实现:
public class SafePriorityQueue<e> {
private final PriorityQueue<e> queue;
private final ReentrantLock lock = new ReentrantLock();
private final Comparator super E> comparator;
public SafePriorityQueue() {
this.queue = new PriorityQueue();
this.comparator = null;
}
public SafePriorityQueue(Comparator super E> comparator) {
this.queue = new PriorityQueue(comparator);
this.comparator = comparator;
}
public void offer(E e) {
lock.lock();
try {
queue.offer(e);
} finally {
lock.unlock();
}
}
public E poll() {
lock.lock();
try {
return queue.poll();
} finally {
lock.unlock();
}
}
public E peek() {
lock.lock();
try {
return queue.peek();
} finally {
lock.unlock();
}
}
public int size() {
lock.lock();
try {
return queue.size();
} finally {
lock.unlock();
}
}
public boolean isEmpty() {
lock.lock();
try {
return queue.isEmpty();
} finally {
lock.unlock();
}
}
}</e></e>
对比其他线程安全方案
虽然用 ReentrantLock 封装是可控且清晰的方式,但也要注意替代方案的适用性:
-
PriorityBlockingQueue:JDK 自带的无界线程安全优先队列,基于可重入锁 + 条件队列实现,适合大多数生产场景,无需手动加锁 -
Collections.synchronizedCollection(new PriorityQueue()):不推荐——它仅同步单个方法,无法保证复合操作(如先isEmpty()再poll())的原子性 - 更复杂需求(如带超时的阻塞插入/删除)应优先考虑
PriorityBlockingQueue或DelayQueue
不复杂但容易忽略的是:锁的粒度要覆盖整个操作逻辑,且必须严格配对。只要守住这个原则,用 ReentrantLock 包装 PriorityQueue 就能稳妥支持并发访问。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











