返回文章列表

文章

阻塞队列被阻塞时,线程在干嘛?

线程在 BlockingQueue 阻塞时是“睡着”的,不占CPU,CPU 会被调度去执行其他线程

目录
  1. 当一个线程在调用 BlockingQueue 的阻塞方法(如 take()、put() 或带超时的 offer/poll)时,若队列当前状态不满足其操作条件(队列空或队列满),它并不是“空转”或“自旋”消耗 CPU,而是进入了 阻塞/等待 状态,具体过程大致如下:
  2. 1. 高层逻辑:ReentrantLock + Condition
  3. 2. 线程状态
  4. 3. 底层实现机制
  5. 4. 在操作系统层面
  6. 5. 小结
  7. 📎 参考文章

当一个线程在调用 BlockingQueue 的阻塞方法(如 take()put() 或带超时的 offer/poll)时,若队列当前状态不满足其操作条件(队列空或队列满),它并不是“空转”或“自旋”消耗 CPU,而是进入了 阻塞/等待 状态,具体过程大致如下:#

1. 高层逻辑:ReentrantLock + Condition#

以最常见的 ArrayBlockingQueue 为例,其内部通过一个 ReentrantLock 和两个 ConditionnotEmptynotFull)来实现阻塞与唤醒:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果队列为空,就在 notEmpty 上等待
        while (count == 0)
            notEmpty.await();           //←—— 线程在这里被挂起
        E x = dequeue();
        // 唤醒可能在 notFull 上等待的生产者
        notFull.signal();
        return x;
    } finally {
        lock.unlock();
    }
}
  • **调用 **notEmpty.await()
    • 线程会先 释放 已获取的锁(lock.unlock()),
    • 然后进入 WAITING(无限等待)或 TIMED_WAITING(带超时等待)状态,
    • JVM 底层通过 AbstractQueuedSynchronizer(AQS)将该线程 park(即挂起)到 notEmpty 条件队列中,不占用 CPU。

2. 线程状态#

  • 在阻塞期间(await() 之后、被唤醒之前),该线程的 Thread.getState() 会是 WAITINGTIMED_WAITING
  • 它不再参与 CPU 调度,直到:
    1. 被其它线程 signal()/signalAll() 唤醒(例如生产者执行了 notEmpty.signal()),
    2. 或者在带超时的方法中达到超时,自动唤醒。

3. 底层实现机制#

  • LockSupport.park() / unpark()
    • AQS 在实现 Condition.await() 时,会调用 LockSupport.park(this) 挂起线程;
    • Condition.signal() 则会调用 LockSupport.unpark(thread),将目标线程从停顿状态唤醒。
  • **线程重入 **lock.lockInterruptibly()
    • 在被唤醒后,线程会首先尝试 重新获取 ReentrantLock 才能继续执行后续代码。
    • 如果在等待期间被中断,它会从 await() 抛出 InterruptedException 并重新获取锁,然后退出。

4. 在操作系统层面#

  • 虽然 Java 在用户态通过 LockSupport.park() 管理线程挂起,但最终仍依赖 OS 的 线程调度器
    • park() 的线程会让出 CPU,无上下文切换到内核态进行真正的阻塞,
    • 直到被 unpark()(或者中断、超时)后,OS 再将其唤醒并放回可运行队列。

5. 小结#

  1. 不占 CPU:线程在 BlockingQueue 阻塞时是“睡着”的,CPU 会调度去执行其他线程。
  2. Condition 等待队列:Java 用 ReentrantLock + Condition(基于 AQS)实现挂起与唤醒。
  3. LockSupport.park/unpark:底层调用它进行线程挂起与恢复。
  4. 重获锁:被唤醒后,线程先拿回锁再继续操作。 了解这些机制,能帮助你:
  • 优化并发性能(减少不必要的阻塞),
  • 在 debug/诊断中正确识别线程状态(如在 jstack 中看到 WAITING),
  • 并在必要时使用超时或非阻塞方法(offer/poll)来设计更健壮的系统。

📎 参考文章#