priorityqueue不是线程安全的,因其内部无锁,offer/poll/peek直接读写数组,多线程下易破坏堆结构,引发arrayindexoutofboundsexception或错误结果;应使用priorityblockingqueue。

PriorityQueue 为什么不是线程安全的
因为 PriorityQueue 内部没有加锁,所有操作(offer、poll、peek)都直接读写数组,多线程并发时可能破坏堆结构,导致 ArrayIndexOutOfBoundsException 或返回错误最小值。
- 常见错误现象:单线程正常,一上多线程就偶尔
NullPointerException或死循环 —— 很可能是多个线程同时触发了resize和元素下沉/上浮冲突 - 如果需要线程安全,别自己加
synchronized块包住整个操作链(容易漏或锁粒度太大),直接用PriorityBlockingQueue -
PriorityBlockingQueue底层用的是可重入锁 + 条件队列,不扩容时性能接近PriorityQueue,但注意它不支持 null 元素,这点和PriorityQueue一致
add()、offer()、put() 的区别在哪
三者看起来都“加元素”,但行为边界完全不同:add() 和 offer() 是 PriorityQueue 自己的方法,put() 根本不属于它 —— 是 PriorityBlockingQueue 的阻塞方法。
-
add():失败时抛IllegalStateException(仅当队列满时,但PriorityQueue默认无界,所以几乎不会抛) -
offer():总是返回boolean,对PriorityQueue来说永远是true(无界 + 不检查容量) -
put():只存在于PriorityBlockingQueue,会阻塞直到有空间(虽然它也无界,但为接口统一保留)—— 实际不会阻塞,但调用它意味着你已切换到阻塞队列语义 - 误用
put()在PriorityQueue上会导致编译错误:Cannot resolve method 'put(E)'
自定义比较器时,compare() 返回 0 的后果
返回 0 表示两个元素“相等”,但 PriorityQueue 不保证相等元素的顺序,且在堆调整中可能跳过必要交换,导致逻辑错乱 —— 尤其当你依赖“相同优先级下按插入顺序处理”时。
- 典型场景:任务调度,优先级相同,希望先提交的先执行。若
compare()对两个不同任务返回 0,它们在堆里可能被当作同一节点处理,poll()可能跳过其中一个 - 正确做法:在优先级相同时,用插入序号或对象哈希码做二次比较,确保
compare()永远不返回 0(除非两个对象真正语义相等且可互换) - 示例:避免
return Integer.compare(a.priority, b.priority);,改用return Integer.compare(a.priority, b.priority) != 0 ? Integer.compare(a.priority, b.priority) : Integer.compare(a.seqId, b.seqId);
底层数组扩容机制怎么影响性能
PriorityQueue 初始容量是 11,每次扩容为 oldCapacity + (oldCapacity (即 1.5 倍),扩容本身不慢,但频繁扩容会引发大量数组拷贝和堆重建,尤其在批量 <code>offer() 时。
立即学习“Java免费学习笔记(深入)”;
- 容易踩的坑:先创建空队列,再 for 循环 add 10000 个元素 —— 中间可能触发 10+ 次扩容,每次都要复制并重新建堆
- 实操建议:预估规模,用带初始容量的构造函数,比如
new PriorityQueue(16384);如果数据已知,改用Arrays.sort()+Arrays.asList()构造,比逐个 offer 快 3–5 倍 - 注意:扩容只影响插入,不影响
poll()性能;但若频繁混合插入/删除,小顶堆的 log(n) 调整开销会叠加扩容成本
堆结构本身不难理解,难的是边界条件 —— 比如 comparator 返回 0、多线程没意识到 resize 非原子、或者把 BlockingQueue 的方法当成 PriorityQueue 的用。这些地方一错,问题往往延迟暴露,排查成本远高于写的时候多加一行判断。










