This is Producer Consumer scenario in Java. Producer is writing in ArrayList and Consumer is reading from that. Both are taking lock on ArrayList object before adding or removing elements.
In Consumer class remove method is called on ArrayList before printing last element in list, which kind of wrong here. But can you help me to understand why does it caused deadloack ?
If I put remove call after System.out.println(...), it works well (infinitely Producer and Consumer both works).
Below are 2 lines from Consumer I am talking about
buffer.remove(buffer.size()-1);
System.out.println("Consumed" + " " + buffer.get(buffer.size() - 1) + " size " + buffer.size());
Full Code:
public class Test {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>(10);
Producer producer = new Producer(arrayList);
Consumer consumer = new Consumer(arrayList);
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(producer);
executorService.submit(consumer);
}
}
class Producer implements Runnable{
private final ArrayList<Integer> buffer;
public Producer(ArrayList<Integer> arrayList){
this.buffer = arrayList;
}
public void run(){
while (true) {
synchronized (buffer) {
while (isFull(buffer)) {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer.add(1);
System.out.println("Produced" + " " + buffer.get(buffer.size() - 1) + " size " + buffer.size());
buffer.notifyAll();
}
}
}
private boolean isFull(ArrayList<Integer> buffer) {
return buffer.size() == 10;
}
}
class Consumer implements Runnable{
private final ArrayList<Integer> buffer;
public Consumer(ArrayList<Integer> buffer) {
this.buffer = buffer;
}
public void run(){
while (true) {
synchronized (buffer) {
while (isEmpty(buffer)) {
try {
buffer.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer.remove(buffer.size()-1);
System.out.println("Consumed" + " " + buffer.get(buffer.size() - 1) + " size " + buffer.size());
buffer.notifyAll();
}
}
}
private boolean isEmpty(ArrayList<Integer> buffer) {
return buffer.size() == 0;
}
}
Typical OutPut in case of deadloack, after Producer is releasing lock Consumer thread doesnt start processing.
Produced 1 size 1
Produced 1 size 2
Produced 1 size 3
Produced 1 size 4
Produced 1 size 5
Produced 1 size 6
Produced 1 size 7
Produced 1 size 8
Produced 1 size 9
Produced 1 size 10
Consumed 1 size 9
Consumed 1 size 8
Consumed 1 size 7
Consumed 1 size 6
Consumed 1 size 5
Consumed 1 size 4
Consumed 1 size 3
Consumed 1 size 2
Consumed 1 size 1
Produced 1 size 1
Produced 1 size 2
Produced 1 size 3
Produced 1 size 4
Produced 1 size 5
Produced 1 size 6
Produced 1 size 7
Produced 1 size 8
Produced 1 size 9
Produced 1 size 10
JDK : jdk1.8.0_111
Below is Thread dump for Producer and Consumer threads
"pool-1-thread-2" #11 prio=5 os_prio=31 tid=0x00007fb1e8039800 nid=0x3c03 waiting on condition [0x000070000fa72000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000007aac942a0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
"pool-1-thread-1" #10 prio=5 os_prio=31 tid=0x00007fb1e704d800 nid=0x3b03 in Object.wait() [0x000070000f96f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007aac8a998> (a java.util.ArrayList)
at java.lang.Object.wait(Object.java:502)
at com.vipin.threading.Producer.run(Test.java:36)
- locked <0x00000007aac8a998> (a java.util.ArrayList)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Fix is easy as I have explained above it works after changing sequence of remove and sysout , I am looking for explanation why it is causing deadlock.
buffer.get(buffer.size() - 1)
throws an exception when the list is empty.
You would find out about this exception by using Future.get
:
try {
executorService.submit(consumer).get();
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}