priorityblockingqueue线程安全但优先级仅出队时生效,peek()不保证实时性,需显式传comparator防溢出和空指针,无界容量易oom,不支持超时轮询和优先级截断。

PriorityBlockingQueue 是线程安全的,但不保证优先级实时生效
它底层用的是 ReentrantLock + 可重入锁保护的堆结构,插入、删除都加锁,所以多个线程操作不会破坏队列结构。但注意:优先级只在出队(poll()、take())时才体现,入队顺序不影响堆内排序时机——新元素插入后会立即上浮/下沉调整,但这个过程是原子的,你无需手动同步。
常见错误现象:peek() 返回的不一定是“当前最高优先级”,而是堆顶元素;如果多个线程反复 offer() 后立刻 peek(),可能看到旧值——因为 peek() 不触发重排,只是读取堆顶引用。
- 使用场景:任务调度器、日志分级提交、带权重的消息分发
- 别拿它做“实时排行榜”:比如想随时拿到 top3,得自己加锁+拷贝+排序,
PriorityBlockingQueue不提供快照能力 - 构造时传
Comparator比实现Comparable更灵活,尤其当你不能改写元素类时
元素必须可比较,否则运行时报 ClassCastException
如果元素没实现 Comparable,又没传 Comparator,第一次 offer() 就抛异常:java.lang.ClassCastException: class X cannot be cast to class java.lang.Comparable。这不是编译期检查,容易漏测。
示例中常有人写错比较逻辑:
立即学习“Java免费学习笔记(深入)”;
new PriorityBlockingQueue<Task>((a, b) -> a.priority - b.priority)
这有整数溢出风险(比如 a.priority = Integer.MAX_VALUE,b.priority = -1),应改用 Integer.compare(a.priority, b.priority)。
- 推荐始终显式传
Comparator,避免依赖元素自身compareTo() - 如果元素字段是
double或BigDecimal,别用==或原始减法,用Double.compare()等 - 注意
null值:默认Comparator不接受null,若业务允许空优先级,得自己处理
take() 和 poll(long, TimeUnit) 的阻塞行为差异影响超时控制
take() 会一直等,直到有元素;poll(timeout, unit) 超时返回 null。但很多人忽略:即使队列非空,poll(0, TimeUnit.NANOSECONDS) 也**不保证立即返回元素**——它是“尽力非阻塞”,底层调用的是 tryLock(),失败就直接返回 null,哪怕队列里有十个元素。
- 需要严格超时控制时,别用
poll(0, ...)做轮询,它可能跳过可用数据 - 如果业务能容忍“最多等 100ms”,用
poll(100, TimeUnit.MILLISECONDS)更可靠 -
drainTo(Collection)是批量出队,但它不支持按优先级截断(比如只要前 5 个),得自己循环poll()
容量无界,OOM 风险比想象中来得快
PriorityBlockingQueue 默认无界(initialCapacity=11,但自动扩容),生产环境不设上限等于给内存泄漏开绿灯。尤其当消费者慢于生产者,或优先级设计不合理(比如低优先级任务永远排不上),队列会持续膨胀。
没有类似 ArrayBlockingQueue 的拒绝策略,也不能传 RejectedExecutionHandler。一旦满,offer() 仍成功(因为无界),但实际已埋雷。
- 监控关键指标:队列 size、GC 频率、老年代占用——突然升高往往意味着消费卡住
- 若需限流,得在外层加信号量或用
Semaphore控制生产速率 - 别依赖
remainingCapacity():它永远返回Integer.MAX_VALUE,毫无意义
优先级队列真正的复杂点不在并发控制,而在“优先级语义是否被业务准确表达”。比如时间戳越小越紧急,但有人写成越大越紧急;再比如多个维度要加权,却只比了一个字段。这些逻辑错位,锁再严也没用。










