linkedblockingqueue 的 take() 方法在队列为空时使线程进入 waiting 状态并释放锁,依赖 reentrantlock 与 notempty 条件队列实现阻塞等待,唤醒后需重新加锁并检查元素是否存在。

LinkedBlockingQueue 的 take() 方法在队列为空时,会通过 AQS(AbstractQueuedSynchronizer)的条件队列机制,让当前线程进入 WAITING 状态并释放锁,直到有元素入队后被唤醒。
底层依赖 ReentrantLock 和 Condition
LinkedBlockingQueue 内部使用一个 ReentrantLock(默认为非公平锁)保护队列操作,并维护两个 Condition 对象:
-
notEmpty:供消费者等待——当队列为空时,take()调用notEmpty.await()挂起线程 -
notFull:供生产者等待——当队列满时,put()调用notFull.await()
这两个 Condition 都基于同一个 ReentrantLock,但各自维护独立的等待队列。
take() 执行流程简述
调用 take() 时,线程会:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 尝试获取队列的锁(lock.lock())
- 检查队列是否为空;若为空,调用
notEmpty.await() -
await()会将当前线程包装成 Node 加入notEmpty的条件队列,并自动释放锁 - 线程状态变为 WAITING,不再参与 CPU 调度
- 当其他线程调用
put()或offer()成功插入元素后,会触发notEmpty.signal(),唤醒条件队列中等待的第一个线程 - 被唤醒的线程重新竞争锁,成功获取后继续执行取值逻辑并返回元素
挂起与唤醒是协作式的,不是轮询
不同于忙等待(busy-waiting),take() 不消耗 CPU:它不循环检查队列状态,而是交由 JVM 线程调度器管理。线程挂起后,仅当满足以下任一条件才会恢复:
- 其他线程向队列添加了至少一个元素,并调用了
signal()或signalAll() - 当前线程被中断(抛出
InterruptedException)
注意:唤醒不保证“刚唤醒就立刻取到元素”——因为可能有多个消费者同时被唤醒,或存在并发修改,所以唤醒后仍需再次加锁并检查队列是否真有元素(即典型的 await + while 循环模式,源码中已封装好)。
实际写代码时无需手动处理挂起逻辑
你只需直接调用 queue.take(),JDK 已完整封装线程挂起、等待、唤醒、异常处理等细节。例如:
LinkedBlockingQueue<string> queue = new LinkedBlockingQueue();
// 消费者
new Thread(() -> {
try {
String msg = queue.take(); // 队列空时自动阻塞
System.out.println("收到:" + msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 生产者
new Thread(() -> {
try {
Thread.sleep(1000);
queue.put("hello"); // 此刻唤醒等待的消费者
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
</string>
这段代码中,消费者线程启动后立即在 take() 处挂起,1 秒后生产者放入元素,消费者被唤醒并打印结果。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










