我不明白 Java Concurrency in Practice 书中的以下片段:
当只有一个线程可以取得进展时使用 notifyAll 是低效的 - 有时有点,有时很严重。如果有 10 个线程在条件队列上等待,调用 notifyAll 会导致每个线程都醒来并争夺锁;然后他们中的大多数或全部会马上回去睡觉。这意味着每个事件都会进行大量上下文切换和大量争用锁获取,从而使(可能)单个线程能够取得进展。(在最坏的情况下,使用 notifyAll 会导致 O(n 2 ) 唤醒,其中 n 就足够了。)
示例代码在清单 14.6 中:
@ThreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {
// CONDITION PREDICATE: not-full (!isFull())
// CONDITION PREDICATE: not-empty (!isEmpty())
public BoundedBuffer(int size) { super(size); }
// BLOCKS-UNTIL: not-full
public synchronized void put(V v) throws InterruptedException {
while (isFull())
wait();
doPut(v);
notifyAll();
}
// BLOCKS-UNTIL: not-empty
public synchronized V take() throws InterruptedException {
while (isEmpty())
wait();
V v = doTake();
notifyAll();
return v;
}
}
例如,我们可以有以下事件序列:
- 两个消费者线程试图从缓冲区中获取一个对象,缓冲区是空的,所以它们被挂起。
- 10 个生产者将 10 个对象放入缓冲区,缓冲区容量为 10。
- 100001 个生产者尝试将 100001 个对象放入缓冲区,缓冲区已满,因此它们被挂起。
- 第一个消费者从缓冲区中获取一个对象并调用 notifyAll。
- 生产者将对象放入缓冲区并调用 notifyAll,缓冲区已满。
现在只有一个线程可以取得进展——消费者线程。我们还有100000个生产者,他们无法进步。
我不明白为什么在最坏的情况下会有 O(n 2 ) 唤醒,在可以取得进展的线程被唤醒之前。
我认为最坏的情况是以下序列
- 所有线程都被唤醒(因为 notifyAll)。我们“使用”了 O(n) 唤醒。
- 一个生产者线程获得锁,其他线程被挂起。生产者线程无法取得进展,因此它被挂起并释放锁。
- 现在只有一个生产者线程被唤醒,因为使用了不同的机制(线程恢复执行,因为它获得了锁——但这次没有调用 notifyAll)。我们仅“使用” O(1) 唤醒。
- 第二个生产者无法取得进展,所以它被挂起并释放锁。
- 所有其他等待的生产者都会发生类似的事件。
- 最后,可以进行的线程(消费者线程)被唤醒。
我认为我们“使用”了 O(n) + O(n)*O(1) = O(n) 唤醒。
书中有错误,还是我在这里遗漏了什么?