当list.remove同时在2个线程中运行时,IndexOutOfBoundsException(并发)

时间:2016-04-07 17:51:45

标签: java multithreading arraylist concurrency synchronization

我在main方法中有一个列表,我想写两个线程来使用这个列表。有时我会在synchronized块中捕获IndexOutOfBoundsException(当线程调用remove方法时)。

主要方法:

public class PC {
    public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        new Costumer("c1", strings).start();
        new Costumer("c2", strings).start();
        new Producer("p1", strings).start();
        new Producer("p2", strings).start();
        new Producer("p3", strings).start();
        new Producer("p4", strings).start();
    }
}

客户类:

class Costumer extends Thread {

    List<String> strings;
    public Costumer(String n, List<String> strings) {
        super(n);
        this.strings = strings;
    }
    @Override
    public void run() {
        while (true) {
            synchronized (strings) {
                try {
                    if (strings.isEmpty()) {
                        strings.wait();
                    }
                    strings.remove(0); // <- where exception is thrown
                } catch (InterruptedException ex) {
                }
            }
        }
    }
}

制作人类:

class Producer extends Thread {

    List<String> strings;

    public Producer(String n, List<String> strings) {
        super(n);
        this.strings = strings;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (strings) {
                strings.add(String.valueOf(Math.random() * 1000));
                if (strings.size() == 1) {
                    strings.notify();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
            }
        }
    }
}

堆栈追踪:

Exception in thread "c2" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.rangeCheck(Unknown Source)
        at java.util.ArrayList.remove(Unknown Source)
        at Costumer.run(PC.java:40)

1 个答案:

答案 0 :(得分:3)

您的代码中的问题是Costumer类中的if测试必须用while循环替换它,否则您可能会遇到竞争条件问题。确实让我们说我们有一个消费者等待通知,我们有一个消费者在等待字符串锁定,我们有生产者对字符串进行锁定并添加一个新字符串并调用notify,因为我们有没有更多的字符串因此,一旦它释放锁定,让我们说等待锁定的消费者首先获得锁定(是的,不要忘记已经通知的消费者仍然需要获得锁定并且无需获得锁定锁定第一个),然后删除一个字符串,然后第二个消费者(消费者已通知的消费者)将从strings.wait()开始,并将调用strings.remove(0)而不检查它是否为空然后你会得到IndexOutOfBoundsException

换句话说,代码应为:

@Override
public void run() {
    while (true) {
        synchronized (strings) {
            try {
                while (strings.isEmpty()) {
                    strings.wait();
                }
                strings.remove(0);
            } catch (InterruptedException ex) {
            }
        }
    }
}

无论如何,将你的条件包装成while循环以避免像这样的奇怪错误是一个好习惯。您可以查看在ArrayBlockingQueue之类的类中完成的操作,例如,在while循环中检查所有条件。