notifyAll操作的复杂性

时间:2013-04-06 15:58:46

标签: java multithreading concurrency

我不理解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;
    }
}

例如,我们可以有以下事件序列:

  1. 两个消费者线程尝试从缓冲区获取一个对象,缓冲区为空,因此它们被挂起。
  2. 10个生产者将10个对象放入缓冲区,缓冲区容量为10。
  3. 100001生产者尝试将100001对象放入缓冲区,缓冲区已满,因此它们被暂停。
  4. 第一个使用者从缓冲区获取一个对象并调用notifyAll。
  5. 生产者将一个对象放入缓冲区并调用notifyAll,缓冲区已满。
  6. 现在只有一个线程可以取得进展 - 消费者线程。我们还有10万名生产者,他们无法取得进步。

    我不明白为什么在最坏的情况下会有O(n 2 )唤醒,在可以取得进展的线程被唤醒之前。

    我认为最坏的情况是以下序列

    1. 所有线程都被唤醒(因为notifyAll)。我们“使用”O(n)唤醒。
    2. 生产者线程获取锁定,其他线程被挂起。生产者线程无法取得进展,因此它被暂停并释放锁定。
    3. 现在只唤醒了一个生产者线程,因为使用了不同的机制(一个线程恢复执行,因为它获得了锁定 - 但这次notifyAll 不是叫)。我们“只使用”O(1)唤醒。
    4. 第二个生产者无法取得进展,因此它被暂停并释放锁定。
    5. 类似的事件发生在所有其他等待的生产者身上。
    6. 最后,可以取得进展的线程(消费者线程)被唤醒。
    7. 我认为我们“使用”O(n)+ O(n)* O(1)= O(n)唤醒。

      书中是否有错误,或者我在这里遗漏了什么?

2 个答案:

答案 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)