bigywater.tech
Java · 387

DelayQueue:到期元素才会变得可取

DelayQueue 是“到期才可取”的优先级队列,常被用来写本地缓存过期和定时任务。这篇量出它的语义细节、compareTo 写错时的队头阻塞、到期精度、多消费者行为和取消成本。

1900 ms
compareTo 与到期顺序不一致时,B 比到期时刻晚被取到的时间
0.34 ms
取到元素时比到期时刻晚的中位数(p99 约 5–8 ms)
31788 ns
队列里 10 万个元素时 DelayQueue.remove 的单次成本(1000 个时约 1111 ns)

已到期才可取

实测 · 本机执行输出
  放入 a(200 ms)、b(400 ms)、c(600 ms);t=0:size() = 3,isEmpty() = false,peek() = a(堆顶是最早到期的),poll() = null
  iterator 遍历:[a, c, b](含未到期元素),remainingCapacity() = 2147483647
  take() 返回 a,等了约 187 ms(预期 200)
  再 take() 返回 b,累计约 394 ms(预期 400)
  又睡 300 ms(此时 c 已到期):size() = 1,poll() = c,size() = 0

队头阻塞

实测 · 本机执行输出
  400 ms 后(B 已到期 300 ms):poll() = null,size() = 2,peek() = A,peek().getDelay = 1589 ms
  poll(3 s) 返回 A,等了约 1581 ms;之后 poll() 取到 B,此时 B 比它的到期时刻晚了约 1900 ms
  对照:compareTo 按到期时间比较,400 ms 后 poll() = B

到期精度

实测 · 本机执行输出
  空闲:取到元素时比到期时间晚(微秒)p50 = 340,p99 = 7890,max = 7919
  8 个空转线程:取到元素时比到期时间晚(微秒)p50 = 343,p99 = 4907,max = 4994

多个消费者

实测 · 本机执行输出
  500 ms 后:取到元素的消费者 1 个,仍在等待的消费者 3 个

取消成本

实测 · 本机执行输出
  队列里 1000 个元素(取消最晚到期的 200 个,第 3 次重复):DelayQueue.remove 每次约 1111 ns;ScheduledThreadPoolExecutor(removeOnCancel)cancel 每次约 856 ns
  队列里 10000 个元素(取消最晚到期的 200 个,第 3 次重复):DelayQueue.remove 每次约 3440 ns;ScheduledThreadPoolExecutor(removeOnCancel)cancel 每次约 743 ns
  队列里 100000 个元素(取消最晚到期的 200 个,第 3 次重复):DelayQueue.remove 每次约 31788 ns;ScheduledThreadPoolExecutor(removeOnCancel)cancel 每次约 39 ns

分步演示

DelayQueue
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;10 个逻辑 CPU;到期精度用 System.nanoTime 做时间源,元素按每毫秒 100 个批量到期,尾部延迟含批内排队
    • 没有验证:getDelay 用 System.currentTimeMillis 时系统时钟被调整的行为、DelayQueue 作为本地缓存过期队列的内存与回收、虚拟线程消费者、cancel 位置不同(随机、最早到期)时的 remove 成本
    京ICP备2026067502号-1京公网安备11010502063487号