应优先使用take()而非poll():take()主动让出CPU、支持中断响应、避免忙循环,而poll()易致CPU飙升且中断处理复杂;ThreadPoolExecutor源码也印证此设计。

take() 会真正释放 CPU,poll() 容易导致忙循环
当队列为空时,take() 会让当前线程进入 WAITING 状态,主动让出 CPU 时间片;而 poll() 立即返回 null,如果写成 while (true) { Runnable task = queue.poll(); if (task != null) execute(task); },线程就会在空队列时反复执行、空转,CPU 使用率飙升到 100%。
这不是理论风险——实际压测中,用 poll() 轮询的 Worker 线程在无任务时平均消耗 0.8–1.2 个逻辑核,而用 take() 的几乎为 0%。
take() 天然支持线程中断和优雅退出
take() 在等待过程中能响应 Thread.interrupt(),并抛出 InterruptedException;这使得线程池关闭时,Worker 可以立即跳出循环、清理资源、终止。
poll() 不阻塞,也就无法被中断打断,你得额外加标志位 + sleep + 判中断,代码变复杂,还容易漏掉唤醒时机。
常见错误包括:
- 忘记在 catch
InterruptedException后恢复中断状态(Thread.currentThread().interrupt()) - 用
poll(timeout, unit)替代take(),但 timeout 设得太小(如 1ms),仍接近忙等 - 在 finally 块里没重置中断状态,导致后续中断失效
ThreadPoolExecutor 源码已按此逻辑设计
翻看 JDK 的 ThreadPoolExecutor.getTask() 实现就能看到:只有当线程数 > corePoolSize 或启用了 allowCoreThreadTimeOut,才用带超时的 poll(keepAliveTime, ...);否则一律走 workQueue.take()。
这意味着:
- 核心线程默认永不超时,靠
take()挂起,省电又及时 - 非核心线程用带超时的
poll()是为了控制存活时间,不是为了“更灵活” - 如果你自己实现 Worker 循环,照搬这个判断逻辑比自行发明轮询策略更稳妥
注意 null 值和异常边界
take() 绝不会返回 null,所以后续无需判空;但要注意它可能抛出 InterruptedException,且某些实现(如 LinkedBlockingQueue)在队列被关闭或中断时也可能抛 IllegalStateException。
poll() 返回 null 是合法行为,但容易掩盖 NPE 风险——比如后续直接调用 task.run() 而没检查,运行时报错才暴露。
另外:BlockingQueue 明确不接受 null 元素,往里 put(null) 或 add(null) 会触发 NullPointerException,这点和 poll() 的返回值 null 完全无关,别混淆。
take() 的阻塞语义会和这些机制产生微妙耦合——比如 ScheduledThreadPoolExecutor 就必须绕开纯 take(),改用带超时的轮询来兼顾延迟精度。

















