java - 为什么LinkedBlockingQueue的put()中有一个while循环
问题描述
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
为什么会有一个while循环?
所有的 put 线程都被 putLock 关闭。
当等待线程持有 putLock 时,没有线程可以增加“计数”。
解决方案
有一个基本属性await
(也适用于通过synchronized
和使用的内在锁定Object.wait
),您必须了解:
当您调用 时await
,您正在释放与¹关联的锁Condition
。没有办法绕过它,否则,没有人可以获取锁,满足条件并调用signal
它。
当您的等待线程收到信号时,它不会立即取回锁。这是不可能的,因为调用的线程signal
仍然拥有它。相反,接收者将尝试重新获取锁,这与调用 没有太大区别lockInterruptibly()
。
但是这个线程不一定是唯一试图获取锁的线程。它甚至不必是第一个。另一个线程可能在put
发出信号之前到达并等待锁定lockInterruptibly()
。因此,即使锁是公平的(锁通常不是),发出信号的线程也没有优先级。即使您给予信号线程优先级,也可能有多个线程因不同原因被信号。
所以到达的另一个线程put
可以在发出信号的线程之前获得锁,发现有空间,并存储元素而无需打扰信号。然后,当信号线程获得锁时,条件不再满足。所以一个有信号的线程永远不能仅仅因为它接收到一个信号就依赖条件的有效性,因此必须重新检查条件,await
如果没有满足则再次调用。
这使得检查循环中的条件成为 using 的标准习惯用法await
,如interface中所述Condition
,以及Object.wait
使用内部监视器的情况,只是为了完整性。换句话说,这甚至不是特定于特定 API 的。
由于无论如何都必须在循环中预先检查和重新检查条件,因此规范甚至允许虚假唤醒,即线程从等待操作返回而没有实际接收到信号的事件。这可以简化某些平台的锁实现,同时不会改变必须使用锁的方式。
¹ 需要强调的是,当持有多个锁时,只会释放与条件关联的锁。
推荐阅读
- php - 调用数组中的成员函数 get_id()... 返回致命错误
- c++ - 原子和 `errno`:使用 C++11 原子读取受保护的 errno 值是否合理?
- bash - 如何根据bash中的元素值拆分数组
- javascript - 如何从大型多维 JavaScript 数组中返回一个元素?
- bash - 在 macOS Mojave 上运行 osascript -e [command] 时出现奇怪的错误
- java - JavaFX,如何防止在 ScrollPane 焦点期间调整标签文本大小?
- machine-learning - 是否有一种标准方法来计算振荡信号中的重复次数?
- google-apps-script - 仅通过 google 应用程序脚本将新数据从 google 工作表导入到另一个工作表
- google-sheets - 在当前日期之后查找下一堂课的日期,参考另一张纸
- javascript - thymeleaf th:onclick 看不到 javascript 函数