生产者 - 消费者在单个消费者和任意数量的生产者的情况下仅使用notify()

时间:2015-11-26 13:07:12

标签: java concurrency java.util.concurrent

以下是我实施Producer-Consumer问题的代码。一切都在使用notifyAll(),但由于效果原因,我希望将所有notifyAll()替换为notify()

我发现通过将notifyAll()更改为notify()来替换这些调用会导致发生死锁。但是,替换这些调用的所有其他尝试都失败了。

是否有一些聪明的方法可以用notify()替换这些调用,使下面的代码能够与单个Consumer和任意数量的Producer一起使用?

public class Buffer
{
    private volatile String content = "";
    private volatile boolean isEmpty = true;

    public synchronized void addItem(String s)
    {
        while(!isEmpty){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        content = s;
        isEmpty = false;
        notifyAll();

    }

    public synchronized String getItem()
    {
        while(isEmpty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        String temp = content;
        isEmpty = true;
        notifyAll();
        return temp;
    }
}

public class Producer implements Runnable
{
    private String greeting;
    private int repetitions;
    private Buffer b;

    public Producer(String aGreeting, int aRepetitions, Buffer aBuffer){
        greeting = aGreeting;
        repetitions = aRepetitions;
        b = aBuffer;
    }

    public void run()
    {
        for(int i = 1; i <= repetitions; i++) {
            b.addItem(greeting + i);
        }
    }
}


public class Consumer implements Runnable {
    private String greeting;
    private Buffer b;
    public Consumer(String aGreeting, Buffer aBuffer){
        greeting = aGreeting;
        b = aBuffer;
    }
    public void run()
    {
        try
        {
            while(true){
                System.out.println(greeting + b.getItem());
                Thread.sleep(100);
            }
        }
        catch(InterruptedException exception){}
    }
}

2 个答案:

答案 0 :(得分:1)

简要说明:虽然notifyAll()通知所有等待的线程,并且notify()通知任何随机线程,但现在这个随机线程可能不是您接下来需要的线程,这可能导致死锁。请参考此示例:

以下步骤导致我们陷入僵局。我们将限制设置为1以保持示例简短。

E1 enqueues an item.
E2 attempts enqueue - checks wait loop - already full - waits

E3 attempts enqueue - checks wait loop - already full - waits

D1 attempts dequeue - and is executing synchronized block
D2 attempts dequeue - blocks on entry to the (synchronized) block - due to D1

D3 attempts dequeue - blocks on entry to the (synchronized) block - due to D1

D1 is executing enqueue - gets the item, calls notify, exits method
The notify happens to wake up E2 (i.e. "any waiting thread")
BUT, D2 enters sync block before E2 can (E2 must reacquire the lock), so E2 blocks on entry to the enqueue sync block
D2 checks wait loop, no more items in queue, so waits

D3 enters block after D2, but before E2, checks wait loop, no more items in queue, so waits

Now there is E3, D2, and D3 waiting!

Finally E2 acquires the lock, enqueues an item, calls notify, exits method

E2's notification wakes E3 (remember any thread can be woken)
E3 checks the wait loop condition, there is already an item in the queue, so waits.
NO MORE THREADS TO CALL NOTIFY and THREE THREADS PERMANENTLY SUSPENDED!

SOLUTION:用notifyAll替换notify

适当参考,notify() instead of notifyAll() for blocking queue

答案 1 :(得分:1)

为了能够使用.notify(),你需要保证,任何可能唤醒线程&#34;消费&#34;整体&#34;原因&#34;通知

,例如使用者(方法.get_item释放缓冲区中单个元素的空间。这是来自消费者的通知的原因。因为您使用单一消费者模型,所以只有生产者(方法.add_item)才能被唤醒,因为这个通知。并且 producer 将整个释放元素用于商店信息。

所以,使用.notify()是消费者可以

另一方面,因为您使用多个制作者,所以来自一个制作人的通知可能会唤醒另一个制作人。当然,一个制片人不会消耗另一个制片人的效果。

所以,使用.notify()生产者是BAD

解决问题的最原生方法是使用不同的通知:一个用于消费者,一个用于生产者。因此,生产者中的通知只能唤醒消费者,消费者会消费生产者存储的信息。使用Condition

可以获得相同关键部分下的不同通知
public class Buffer
{
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); 
    final Condition notEmpty = lock.newCondition(); 

    // `volatile` isn't needed for objects accessed under critical section
    private String content = "";
    private boolean isEmpty = true;

    // Use lock instead of `synchronized`.
    public void addItem(String s)
    {
        lock.lock();
        try {
            while(!isEmpty){
                try {
                    notFull.await(); // Analogue for wait()
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            content = s;
            isEmpty = false;
            notEmpty.signal(); // Analogue for notify()
        } finally {
            lock.unlock();
        }    
    }

    // Use lock instead of `synchronized`.
    public String getItem()
    {
        lock.lock();
        try {
            while(isEmpty) {
                try {
                    notEmpty.await(); // Analogue for wait()
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            String temp = content;
            isEmpty = true;
            notFull.signal(); // Analogue for notify()
            return temp;
        } finally {
            lock.unlock();
        }    
    }
}