我不理解Java Concurrency in Practice一书中的以下片段:
当只有一个线程可以取得进展时使用notifyAll是低效的 - 有时是一点点,有时甚至是如此。如果十个线程在条件队列上等待,则调用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万名生产者,他们无法取得进步。
我不明白为什么在最坏的情况下会有O(n 2 )唤醒,在可以取得进展的线程被唤醒之前。
我认为最坏的情况是以下序列
我认为我们“使用”O(n)+ O(n)* O(1)= O(n)唤醒。
书中是否有错误,或者我在这里遗漏了什么?
答案 0 :(得分:2)
有些东西被放入队列n次。 “n wakeups就足够了”意味着理想情况下,当生产者将某些内容放入缓冲区时,我们会希望通知一个消费者,因此会有n个通知,甚至更好,它们都会无争议。但是,所有等待锁定的线程,包括所有生成器(减去1,执行推送的那个)和所有消费者(无论如何都在等待),每次在队列中丢弃某些东西时都会得到通知,所有争夺锁定的人都会选择胜利者。 (我们甚至没有考虑所选线程必须等待的情况,这只是一个细节。)所以有n次调用notifyAll,每次调用一次,每个notifyAll唤醒多个生产者和多个消费者,这是他们获得O(n 2 )唤醒的地方。
答案 1 :(得分:0)
让我们有n个消费者线程和n个生产者线程,并且缓冲区为空(具有完整缓冲区的示例与此类似)。所有线程都处于准备运行状态(调度程序可以选择要运行的任何线程)。
如果有任何消费者运行-它将进入等待状态。如果有任何生产者运行,它将成功并调用notifyAll()。
最大化wait()调用(和唤醒)数量的情况:
[ [ 'Joe', 'Bob' ], [ 'Joe', 'Bob' ] ]
让我们数一数: 5 + 4 + 3 + 2 +1 = 15
对于n个生产者和n个消费者: n +(n-1)+(n-2)+(n-3)+…+ 1 = 1 + 2 + 3 + 4 + ... + n = arithmetic progression的前n个元素之和= n *(1 + n)/ 2 =(n + n ^ 2)/ 2→O(n ^ 2)