2

I am trying to find out whether it is possible to have a multiple producer / multiple consumer queue where I can use notify() instead of notifyAll(). For example, in the implementation below (source: here) you cannot just simply switch the notifyAll() for notify(). It is not totally obvious why you cannot switch so I will leave it as an teaser to whoever wants to help me out understanding this problem.

So the code below is broken:

public class BlockingQueue {

  private Object lock = new Object();

  private List queue = new LinkedList();
  private int  limit = 10;

  public BlockingQueue(int limit){
    this.limit = limit;
  }


  public void enqueue(Object item)
  throws InterruptedException  {
   synchronized(lock) {
    while(this.queue.size() == this.limit) {
      lock.wait();
    }
    if(this.queue.size() == 0) {
      lock.notify();
    }
    this.queue.add(item);
   }
  }


  public Object dequeue()
  throws InterruptedException{
   synchronized(lock) {
    while(this.queue.size() == 0){
      lock.wait();
    }
    if(this.queue.size() == this.limit){
      lock.notify();
    }

    return this.queue.remove(0);
  }
 }
}
chrisapotek
  • 6,007
  • 14
  • 51
  • 85

3 Answers3

10

The following steps lead us to deadlock. Let's set limit to 1 to keep the example brief.

  • 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: Replace notify with notifyAll

xagyg
  • 9,562
  • 2
  • 32
  • 29
  • So it is impossible to have a blocking queue (multiple consumers and producers) with notify() ? – chrisapotek Dec 08 '12 at 08:48
  • @chrisapotek That is correct. You can only use notify in cases where only one "type" of thread is involved. – xagyg Dec 08 '12 at 08:50
  • Just wanted to know if it was possible somehow to use notify(). – chrisapotek Dec 08 '12 at 08:50
  • 1
    @chrisapotek No problem. As mentioned, it's not possible. Using notify may happen to work (even most of the time), but there is always a chance it could get into the deadlocked state. The example you give is a fairly standard bounded buffer scenario and the general pattern with these is always to use a notifyAll. All patterns like readers/writers, producers/consumers etc. must use notifyAll to avoid deadlock. – xagyg Dec 08 '12 at 09:08
  • OK, what's wrong with a semaphore instead? Waking a dozen threads for one object, then having 11 of them block again almost immediately, does not seem sane to me.. – Martin James Dec 08 '12 at 10:25
  • Can you give an example with a semaphore, Martin? – chrisapotek Dec 08 '12 at 17:31
  • http://stackoverflow.com/questions/8288479/how-to-solve-the-producer-consumer-using-semaphores – Martin James Dec 10 '12 at 23:20
0

Whatever your design, know that notify() wakes up a random thread currently waiting on the lock object, and notifyAll() wakes up all such threads (in random order).

As long as your waiting threads are coded to handle the type of wake up you give them, there'll be no problem.

One is not better then the other: In some situations notify() is the best choice, in others notifyAll() - it depends on the problem being tackled.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I wanted to use `notify()` for a blocking queue, but if you use `notify()` in the code above it can break the queue. Do you see the situation where it breaks? Is it impossible to use notify() with this type of queue? – chrisapotek Dec 08 '12 at 05:54
0

Move notify out of conditional statements in both methods (that conditions are no longer needed), and everything should work correctly.

And, in the original source the methods were synchronized. Why did you changed this to synchronized(lock)? Just to forget to change wait to lock.wait()?

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38