synchronousqueue 不能存任务,因其无内部容量,offer()仅当有线程正执行poll()/take()时才成功,实现“一手交任务、一手接执行”的零延迟移交;需配合corepoolsize==maximumpoolsize及callerrunspolicy等配置方可发挥优势。

为什么 SynchronousQueue 不能存任务,反而适合零延迟移交
SynchronousQueue 的核心特性是它没有内部容量——offer() 只有在有另一个线程正调用 poll()(或 take())时才返回 true;否则立刻失败或阻塞。这意味着:任务不会“排队”,而必须被**当场消费**。这正是零延迟移交的物理基础——不是“先存再取”,而是“一手交任务,一手接执行”。
但注意:这只有在线程池中已有空闲工作线程、且该线程恰好处于 take() 等待状态时才成立。如果所有线程都在忙,新任务会触发线程池的拒绝策略(默认是 AbortPolicy),而不是等待。
用 SynchronousQueue 构建 FixedThreadPool 的正确姿势
常见误区是直接 new ThreadPoolExecutor(..., new SynchronousQueue()),却忽略核心参数配合。SynchronousQueue 本身不缓冲,所以必须确保:当任务到来时,总有线程能立刻响应。这就要求线程池的 corePoolSize 和 maximumPoolSize 相等,且拒绝策略设为 CallerRunsPolicy 或自定义兜底逻辑。
-
corePoolSize == maximumPoolSize:避免任务因线程扩容延迟而被拒绝 - 拒绝策略不用
AbortPolicy:否则无空闲线程时直接抛RejectedExecutionException - 保持
keepAliveTime = 0L:无意义(因为线程数恒定) - 推荐构造方式:
new ThreadPoolExecutor(n, n, 0L, TimeUnit.MILLISECONDS, new SynchronousQueue(), new CallerRunsPolicy())
实际移交过程:从 submit() 到 run() 中间发生了什么
当你调用 executor.submit(runnable),流程是:线程 A 调用 queue.offer(runnable) → 若此时线程 B 正在 getTask() 中执行 queue.poll()(或阻塞于 take()),则 offer 成功,runnable 直接交给线程 B 执行,**无内存拷贝、无队列入队出队开销**。
关键点在于:这个“移交”是通过线程间直接的 LockSupport.unpark / park 协作完成的,底层依赖 TransferQueue 的 transfer() 方法。所以它比 ArrayBlockingQueue 少了至少两次 volatile 写 + 一次 CAS 操作。
- 成功移交时,任务对象引用直接从生产者栈帧传入消费者线程的局部变量,不经过堆上队列节点
- 若无空闲线程,
offer()返回 false,触发拒绝策略——这时才是真正的“延迟点”,而非队列本身 - 监控时可观察
executor.getRejectedExecutionHandler()是否被频繁调用,这是容量瓶颈信号
容易被忽略的陷阱:CPU 密集型任务会让移交失效
SynchronousQueue 的零延迟只对“移交动作”成立,不代表任务执行没延迟。如果线程池里全是 CPU 密集型任务,线程长时间占用 CPU,新任务来了没人能及时 take(),offer() 就会失败,最终走拒绝逻辑。
- 这不是 SynchronousQueue 的问题,而是线程池配置与负载不匹配
- 解决方法:按 CPU 核心数设置
n(如Runtime.getRuntime().availableProcessors()),并确保任务确实不阻塞 - 混合 I/O 任务时,务必搭配
allowCoreThreadTimeOut(true)或改用其他队列,否则空闲线程无法释放,资源浪费 - 调试时可打印
executor.getActiveCount()和executor.getCompletedTaskCount()差值,判断是否长期满载
真正难的不是配对 SynchronousQueue 和线程池,而是让“移交瞬间”和“执行时机”严丝合缝——这取决于你对任务行为的预判,而不是队列本身有多快。










