I was trying to learn multi threading, and was trying out a simple producer/consumer pattern with wait and notify. When i split the pattern with two consume and one produce i get an ArrayIndexOutOfBounds Exception which is not clear. The issue does not occur always it occurs at times. I am using a I3 processor.
I have tried added an if block to check if the count variable is going below or above the declared size and still the issue persists.
private static Object key = new Object();
private static int[] buffer;
private volatile static Integer count;
static class Consumer {
void consume() {
synchronized (key) {
if (isEmpty()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[--count] = 0;
key.notify();
}
}
public boolean isEmpty() {
return count == 0;
}
}
static class Producer {
void produce() {
synchronized (key) {
if (isFull()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[count++] = 1;
key.notify();
}
}
public boolean isFull() {
return count == buffer.length;
}
}
public static void main(String[] args) throws InterruptedException {
buffer = new int[10];
count = 0;
Producer producer = new Producer();
Consumer consumer = new Consumer();
Runnable produce = () -> {
for (int i = 0; i < 1500; i++)
producer.produce();
System.out.println("Done producing");
};
Runnable consume = () -> {
for (int i = 0; i < 1300; i++)
consumer.consume();
System.out.println("Done consuming");
};
Thread producerWorker = new Thread(produce);
Thread consumerWorker = new Thread(consume);
producerWorker.start();
consumerWorker.start();
//consumerWorker.join();
Runnable checker = () -> {
System.out.println("Lanched Delayed Consumer");
for (int i = 0; i < 200; i++)
consumer.consume();
};
Thread delayedConsumer = new Thread(checker);
delayedConsumer.start();
producerWorker.join();
System.out.println("Data in Buffer " + count);
}
}
Expected it to be :
Lanched Delayed Consumer
Done consuming
Done producing
Data in Buffer 0
but got :
Lanched Delayed Consumer
Exception in thread "Thread-1" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
at com.multi.thread.waitandnotify.WaitNotifyRunner$Consumer.consume(WaitNotifyRunner.java:27)
at com.multi.thread.waitandnotify.WaitNotifyRunner.lambda$1(WaitNotifyRunner.java:78)
at java.base/java.lang.Thread.run(Thread.java:835)
java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 10
at com.multi.thread.waitandnotify.WaitNotifyRunner$Producer.produce(WaitNotifyRunner.java:55)
at com.multi.thread.waitandnotify.WaitNotifyRunner.lambda$0(WaitNotifyRunner.java:73)
at java.base/java.lang.Thread.run(Thread.java:835)
Data in Buffer 0
The problem is that:
A possible thing that happens is:
You need to recheck the condition after you wake up from a wait
call. Change the if
to a while
. Here's how to do that for the consumer; you need to do the same for the producer.
void consume() {
synchronized (key) {
while (isEmpty()) {
try {
key.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[--count] = 0;
key.notify();
}
}
That way the consumer won't get confused by the notify
from the other consumer thread.
This is also "recommended" by the documentation of Object.wait
:
The recommended approach to waiting is to check the condition being awaited in a while loop around the call to wait, as shown in the example below. Among other things, this approach avoids problems that can be caused by spurious wakeups.
(I quoted "recommended" because it's required to do it this way in order to implement it correctly because wait
can also spuriously wake up without a call to notify
)