bigywater.tech
Java · 388

PriorityBlockingQueue:优先级队列没有容量上限

PriorityBlockingQueue 听上去是“按优先级的 BlockingQueue”,实际有四个常被忽略的边界:同优先级无序、遍历顺序不是优先级顺序、元素字段不能变、没有容量上限。这篇逐项量出来,并给出让它可用的做法。

[t0, t11, …, t1]
12 个优先级相同的任务入队 t0..t11 后的出队顺序(不是先进先出)
> 3000 ms vs 2224 ms
持续高优先级到达时,低优先级任务的等待:纯 prio 3 秒内未被取到,加老化后 2224 ms
62809
-Xmx64m 下无界 put 1 KiB 元素,抛出 OutOfMemoryError 前放入的个数

同优先级与遍历顺序

实测 · 本机执行输出
  12 个优先级都是 1 的任务按 t0..t11 入队,出队顺序:[t0, t11, t10, t9, t8, t7, t6, t5, t4, t3, t2, t1](是否与入队顺序相同:false)
  加上序号作第二比较键(thenComparingLong(seq)):[t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11]
  依次放入 9 3 7 1 8 2 6:toString() = [1, 3, 2, 9, 8, 7, 6];poll 顺序 = [1, 2, 3, 6, 7, 8, 9];stream 的顺序 = [1, 3, 2, 9, 8, 7, 6]
  没有比较器时 put(new Object()):ClassCastException:class java.lang.Object cannot be cast to class java.lang.Comparable (java.lang.Object and …

修改已入队元素

实测 · 本机执行输出
  j0..j5 优先级 10..15;把 j5 的优先级改成 1 之后(没有重新入队):peek() = j0:10,poll 顺序 = [j0:10, j5:1, j1:11, j2:12, j3:13, j4:14]
  先 remove 再改再 put:poll 顺序 = [j5:1, j0:10, j1:11, j2:12, j3:13, j4:14]

饥饿与老化

实测 · 本机执行输出
  纯按 prio 排序:3 秒内处理了 6003 个任务;低优先级任务3 秒内一直没有被取到,队列里积压 471
  按“创建时间 + prio × 20 ms”排序(越旧越优先):3 秒内处理了 6017 个任务;低优先级任务在 2224 ms 时被取到

无界队列

实测 · 本机执行输出
  无界队列 put 持续放入 1 KiB 数组,-Xmx64m 下放入 62809 个(约 61 MiB)后 OutOfMemoryError:Java heap space
  用 Semaphore(1000) 限界:2 秒内生产 2322,消费 1322,队列里 1000(不会超过 1000),无 OOM

分步演示

PriorityBlockingQueue
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;饥饿实验用了自定义的“创建时间 + prio × 20 ms”比较器作老化(只是示意,不是推荐的参数);OOM 实验用 -Xmx64m
    • 没有验证:PriorityBlockingQueue 的扩容过程(默认容量 11 之后的增长系数)、多个消费者的公平性、与 DelayQueue 组合、吞吐与队列长度的关系
    京ICP备2026067502号-1京公网安备11010502063487号