bigywater.tech
Java · 385

SynchronousQueue:不存储元素的直接交接

SynchronousQueue 是最特别的 BlockingQueue:它没有存储。这篇用真实运行量出它的语义、它在线程池里的效果、公平与非公平的取走顺序,以及直接交接的代价。

0
SynchronousQueue 的容量;生产者阻塞时 size()、peek()、iterator() 都看不到元素
4 / 6
ThreadPoolExecutor(2, 4, SynchronousQueue) 连续提交 10 个阻塞任务时接受 / 拒绝的数量
2.6 µs vs 0.12 µs
每个对象的交接成本:SynchronousQueue 与容量 1024 的 ArrayBlockingQueue

零容量语义

实测 · 本机执行输出
  remainingCapacity() = 0,size() = 0,isEmpty() = true
  没有消费者时 offer("a") = false,poll() = null,peek() = null
  offer("a", 200 ms) = false,耗时约 209 ms
  poll(200 ms) = null,耗时约 210 ms
  生产者已阻塞 300 ms:size() = 0,peek() = null,iterator().hasNext() = false,线程状态 WAITING
  生产者 put("x") 在 320 ms 后返回
  消费者 take() 取到 x
  消费者还没来时 offer("direct") = false
  已有消费者在 take() 等待时 offer("y") = true

线程池里的 SynchronousQueue

实测 · 本机执行输出
  50 个阻塞任务同时提交:池里线程数 50,队列元素数 0,队列类型 SynchronousQueue
  任务结束后空闲线程仍有 50 个(60 秒内不回收)
  ThreadPoolExecutor(2, 4, SynchronousQueue):连续提交 10 个阻塞任务,接受 4 个、拒绝 6 个(第 5 个任务被拒绝)

公平与非公平

实测 · 本机执行输出
  fair = true:先后到达的生产者 1..5 被取走的顺序 [1, 2, 3, 4, 5]
  fair = false:先后到达的生产者 1..5 被取走的顺序 [5, 4, 3, 2, 1]

交接成本

实测 · 本机执行输出
  SynchronousQueue           每个对象约 2598 ns(中位数,共 5196 ms)
  SynchronousQueue(fair)     每个对象约 2397 ns(中位数,共 4793 ms)
  ArrayBlockingQueue(1)      每个对象约 4976 ns(中位数,共 9951 ms)
  ArrayBlockingQueue(1024)   每个对象约 123 ns(中位数,共 246 ms)
  LinkedBlockingQueue        每个对象约 139 ns(中位数,共 277 ms)

分步演示

SynchronousQueue
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12;交接成本是 1 个生产者 + 1 个消费者、2,000,000 个对象、5 轮取中位数的 nanoTime 测量,不是 JMH,数字只用于比较量级
    • 没有验证:多生产者多消费者下的吞吐、不同 CPU 核数与不同 JDK 的差异(JDK 21 的 SynchronousQueue 实现)、虚拟线程下的行为、ThreadPoolExecutor 的 keepAlive 回收(没有等满 60 秒)
    京ICP备2026067502号-1京公网安备11010502063487号