Reputation: 798
I have this code which is a simulation of Producer Consumer problem using a single producer and multiple consumers on a shared Q class object. I am using notify() here not notifyAll() as i need to understand why this code goes into a deadlock or infinite waiting state.
My Point here is : If there is a single producer and multiple consumers then notify() will invoke only one thread in waiting state and rest will stay in the wait() state . The producer will then resume again and hence the code will keep on executing .
Observation : Here all threads producer and consumers go in an infinite waiting state. The code is shown below :
public class ProdConsProb {
public static void main(String[] args) {
Q q = new Q();
Thread producerThread = new Thread(new Producer(q), "producerThread");
Thread consumerThread = new Thread(new Consumer(q), "Consumer1");
Thread consumerAnotherThread = new Thread(new Consumer(q), "Consumer2");
Thread consumerYetAnotherThread = new Thread(new Consumer(q), "Consumer3");
producerThread.start();
consumerThread.start();
consumerAnotherThread.start();
consumerYetAnotherThread.start();
}
}
class Producer implements Runnable {
Q q;
public Producer(Q q) {
this.q = q;
}
@Override
public void run() {
int i = 0;
while (true)
try {
q.setN(i++);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Consumer implements Runnable {
Q q;
public Consumer(Q q) {
this.q = q;
}
@Override
public void run() {
while (true)
try {
q.getN();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Q {
private int n = 0;
boolean valueSet = false;
public synchronized int getN() throws InterruptedException {
while (!valueSet)
{
wait();
}
valueSet = false;
notify();
return n;
}
public synchronized void setN(int n) throws InterruptedException {
while (valueSet == true)
{
wait();
}
this.n = n;
valueSet = true;
notify();
}
}
I had added some sysouts . The logs generated are shown below :
producerThread :: SetN : Valueset is false
producerThread :: Producer inserted 0
producerThread :: SetN : Valueset after is true
producerThread :: SetN : Valueset is true
producerThread wait() ------------ Active
producerThread :: SetN :Wait() Valueset is true
Consumer3 Start :: GetN : Valueset is true
Consumer3 :: Consumer read 0
Consumer3 End :: GetN : Valueset after is false
Consumer3 Start :: GetN : Valueset is false
Consumer3 wait() ------------ Active
Consumer3 :: GetN :Wait() Valueset is false
Consumer2 Start :: GetN : Valueset is false
Consumer2 wait() ------------ Active
Consumer2 :: GetN :Wait() Valueset is false
Consumer1 Start :: GetN : Valueset is false
Consumer1 wait() ------------ Active
Consumer1 :: GetN :Wait() Valueset is false
producerThread wait() ------------- left
producerThread :: Producer inserted 1
producerThread :: SetN : Valueset after is true
producerThread :: SetN : Valueset is true
producerThread wait() ------------ Active
-->> producerThread :: SetN :Wait() Valueset is true
Consumer3 wait() left
Consumer3 :: Consumer read 1
Consumer3 End :: GetN : Valueset after is false
Consumer3 Start :: GetN : Valueset is false
Consumer3 wait() ------------ Active
Consumer3 :: GetN :Wait() Valueset is false
???? Consumer2 wait() left
Consumer2 wait() ------------ Active
Consumer2 :: GetN :Wait() Valueset is false
The weird thing here was once producer notifies after inserting 1, consumer 3 reads the data and notifies producer. Now producer 3 must trigger back from its wait() but customer2 thread leaves its wait() and goes back to wait() .
Note : This code works with notifyAll() but i am looking for a reason as to why it fails with notify().
Upvotes: 1
Views: 220
Reputation: 96394
It fails because producers and consumers are waiting on the same monitor and intrinsic locks don’t support separate conditions. If a notification happens either a producer or a consumer can get it. But a given notification will be applicable only to one or the other. When one gets a notification that only the other can act on then the notification doesn’t do any good: the notified thread wakes up, finds the condition it is waiting for is still false, and goes back to waiting.
If you look at ArrayBlockingQueue, it’s implemented with ReentrantLock, with separate conditions, one for consumers and one for producers, so that this kind of bug can’t happen.
Upvotes: 2