1

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.

xingbin
  • 27,410
  • 9
  • 53
  • 103
Vipin
  • 4,851
  • 3
  • 35
  • 65

2 Answers2

3

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();
}
Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • oh I was expecting that, but was wondering why we dont get this exception in console ? – Vipin Aug 18 '18 at 15:01
  • 1
    It's just how `ExecutorService` handles exceptions thrown by a computation. They are caught, and you need to call `Future.get` to have them rethrown. They are wrapped in an `ExecutionException` and you can call `getCause()` to get the original exception. – Radiodef Aug 18 '18 at 15:02
  • Also see https://stackoverflow.com/questions/3929342/choose-between-executorservices-submit-and-executorservices-execute – Radiodef Aug 18 '18 at 15:14
1

It's not blocked, it throws IndexOutOfBoundsException since you are trying to retrieve from an empty List.

You can add the try catch block in Consumer and print the Exception:

    try {
        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();
            }
        }
    } catch (Exception e) {
        System.out.println(e);
    }

Output:

java.lang.IndexOutOfBoundsException: Index -1 out-of-bounds for length 0
xingbin
  • 27,410
  • 9
  • 53
  • 103
  • Why we dont get this exception in console ? – Vipin Aug 18 '18 at 15:02
  • 1
    @Vipin The exception was catched by ExecutorService. See this https://stackoverflow.com/questions/2248131/handling-exceptions-from-java-executorservice-tasks – xingbin Aug 18 '18 at 15:04
  • 1
    Thanks, both answers are perfect. Accepted answer was submitted few seconds before this. – Vipin Aug 18 '18 at 15:08