Pergunta

I am going through the classical producer-consumer problem using multithreading. I am using wait() and notifyAll() in the code. My question is when notifyAll notifies the other waiting thread to resume, it is not resuming right away. why is that? The code is below

public class ConsumerProducer {

 private  int count;

 public synchronized void consume() {
  while (count == 0) {  // keep waiting if nothing is produced to consume
   try {
    wait(); // give up lock and wait
   } catch (InterruptedException e) {
    // keep trying
   }
  }

  count--;              // consume
  System.out.println(Thread.currentThread().getName() + " after consuming " + count);
 }

 public synchronized void produce() {
  count++;             //produce
  System.out.println(Thread.currentThread().getName() + " after producing " + count);
  notifyAll();         // notify waiting threads to resume
 }

}

Client code:

public class ConsumerProducerTest implements Runnable {

 boolean isConsumer;
 ConsumerProducer cp;

 public ConsumerProducerTest(boolean isConsumer, ConsumerProducer cp) {
  this.isConsumer = isConsumer;
  this.cp = cp;
 }

 public static void main(String[] args) {
  ConsumerProducer cp = new ConsumerProducer(); //shared by both threads to communicate

  Thread producer = new Thread(new ConsumerProducerTest(false, cp));
  Thread consumer = new Thread(new ConsumerProducerTest(true, cp));

  producer.start();
  consumer.start();
  //producer.start();


 }

 @Override
 public void run() {
  for (int i = 1; i <= 10; i++) {
   if (!isConsumer) {
    cp.produce();
   } else {
    cp.consume();
   }
  }

The output is:

Thread-1 after producing 1
Thread-1 after producing 2
Thread-1 after producing 3
Thread-1 after producing 4
Thread-1 after producing 5
Thread-1 after producing 6
Thread-1 after producing 7
Thread-1 after producing 8
Thread-1 after producing 9
Thread-1 after producing 10
Thread-2 after consuming 9
Thread-2 after consuming 8
Thread-2 after consuming 7
Thread-2 after consuming 6
Thread-2 after consuming 5
Thread-2 after consuming 4
Thread-2 after consuming 3
Thread-2 after consuming 2
Thread-2 after consuming 1
Thread-2 after consuming 0

After the first line above is printed, the notifyAll is called and the waiting thread should resume, printing Thread-2 after consuming 0. The problem I am seeing is after Thread-1 (Thread-1 after producing) is finished, Thread-2 is resumed. But both are supposed to happen concurrently? kindly help me out here. Thanks EDIT: having join() inside main method, did not change the output:

 producer.start();
  producer.join();
  consumer.start(); 
Foi útil?

Solução

But both are supposed to happen concurrently?

The problem here is that the production (and the consumption) take very little time. You are seeing what is called a (non critical) race condition and the producer is able to produce all 10 items before the consumer even starts. By race condition I mean that your two threads are racing each other to produce and consume, not a bug in this case.

If you increased your test to (let's say) 100000 items, you would see producing and consuming messages intermixed although even then you might see blocks of producing and consuming messages that were 10 or more in length.

Another thing to try would be to start the consumer first and put a Thread.sleep(10); after you start it so that it is waiting for the producer. Then the consumer would then have time to call wait() and would be moved from WAIT to BLOCKED as soon as the first notifyAll() was called. But even then, the race condition might show all the producing messages before the consuming. That is the asynchronous nature of multi-threaded applications.

p.s. It is always a good pattern to properly handle InterruptedException. You should do something like:

try {
    wait(); // give up lock and wait
} catch (InterruptedException e) {
    // reset the thread interrupt flag
    Thread.currentThread().interrupt();
    // probably stopping the thread is best
    return;
}

Outras dicas

As most people here are telling you, the program output is not incorrect. It is your challenge to understand why.

Calling .notifyAll() does not guarantee that the notifying thread (producer) will stop executing right away, and it does not guarantee that the thread waiting for the notification (the consumer) will begin executing right away. The two threads may execute "concurrently", or they may not. Threads are at the mercy of the operating system's thread scheduler. If you were to increase the number of iterations in the for-loop, you would probably see the messages start interleaving as the operating system swaps the two threads on and off CPU, but they would not necessarily interleave -- nothing says they must -- they simply might. Also the granularity of the interleaving (if it happens at all) is not guaranteed.

Sending a notification just guarantees that if another thread is blocked in "wait", that thread will eventually awake. Also, the consumer will not necessarily wake up 10 times in total. Notifications are only meaningful to threads that are blocked in a "wait" call. It may happen that you send 10 notifications, but only enter and exit a call to .wait once (or never). If you send a notification when no threads are in .wait, that notification is "lost". This is why some other information is usually used in combination with wait/notify, like a count, or a boolean, or a queue.

From the notify (and notifyAll) docs:

The awakened threads will not be able to proceed until the current thread relinquishes the lock on this object. The awakened threads will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened threads enjoy no reliable privilege or disadvantage in being the next thread to lock this object.

What you are seeing is simply how these two threads are being scheduled.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top