ArrayList方法导致Producer Consumer死锁

ArrayList method causing deadlock in Producer Consumer

这是Java中的生产者消费者场景。生产者在 ArrayList 中写入,而消费者正在从中读取。两者都在添加或删除元素之前锁定 ArrayList 对象。

在 Consumer class 中,在打印列表中的最后一个元素之前,在 ArrayList 上调用了 remove 方法,这是一种错误。但是你能帮我理解为什么会导致死锁吗?

如果我在 System.out.println(...) 之后放置 remove 调用,它会很好地工作(无限生产者和消费者都可以工作)。

以下是我正在谈论的 Consumer 中的 2 行

buffer.remove(buffer.size()-1);
System.out.println("Consumed" + " " + buffer.get(buffer.size() - 1) + " size " + buffer.size());

完整代码:

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;
    }
}

发生死锁时的典型输出,在生产者释放锁之后消费者线程不会开始处理。

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

下面是生产者和消费者线程的线程转储

"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)

修复很简单,正如我上面所解释的,它在更改 remove 和 sysout 的顺序后起作用,我正在寻找它导致死锁的原因的解释。

它没有被阻止,它抛出 IndexOutOfBoundsException 因为你正试图从一个空列表中检索。

您可以在 Consumer 中添加 try catch 块并打印异常:

    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);
    }

输出:

java.lang.IndexOutOfBoundsException: Index -1 out-of-bounds for length 0

buffer.get(buffer.size() - 1) 列表为空时抛出异常。

您可以使用 Future.get:

来了解此异常
try {
    executorService.submit(consumer).get();
} catch (ExecutionException | InterruptedException e) {
    e.printStackTrace();
}