使用ArrayList()的多线程获得了ArrayIndexOutOfBoundsException

时间:2018-03-16 14:06:07

标签: java multithreading arraylist concurrency

我已经使用以下代码通过非线程安全对象(这里是ArrayList)运行多个线程:

import java.time.LocalDateTime;
import java.util.List;
import java.util.ArrayList;

public class A implements Runnable {
    String name;

    static  List<Integer> list = new ArrayList();
    private static Object lock = new Object();

    A(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for(int i = 1; i <= 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());
    }
}

由于ArrayList不是线程安全的,因此我期待此代码产生错误的答案。但相反,我得到了这个错误:

Exception in thread "Thread-1" 1003
2401
2799
3799
java.lang.ArrayIndexOutOfBoundsException: 109
    at java.util.ArrayList.add(Unknown Source)
    at threads.A.run(A.java:16)5123

    at java.lang.Thread.run(Unknown Source)
Exception in thread "Thread-5" java.lang.ArrayIndexOutOfBoundsException: 4164
    at java.util.ArrayList.add(Unknown Source)
    at threads.A.run(A.java:16)
    at java.lang.Thread.run(Unknown Source)
6123

任何人都可以向我解释导致此特定错误的原因吗?

2 个答案:

答案 0 :(得分:7)

好吧,您在多线程环境中使用非线程安全集合而没有任何同步。

让我们检查add方法,在那里你得到例外:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

当多个线程同时调用此方法时,ensureCapacityInternal(size + 1)很可能验证是否有足够的空间容纳1个新元素,但是多个线程尝试同时添加一个元素,因此{ {1}}为其中一些人投掷elementData[size++]

答案 1 :(得分:6)

ArrayList不是线程安全的类。

元素的底层存储Object[],它是一个数组。任何数组都需要在编译时预定义的内存中的分配空间。但是,当ArrayList“想要”添加新元素(超出底层数组边界)时,必须完成几件事(在您不知情的情况下)。底层数组获得新的(增加的)长度。将旧数组的每个元素复制到新数组,然后添加新元素。因此,当在多线程环境中使用ArrayIndexOutOfBoundsException时,可以期望ArrayList异常。

您添加的元素太快,因此ArrayList#add() -> grow() -> newCapacity()无法计算为所有元素分配内存的正确容量。

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

在某个时间点,s == elementData.length内的条件ArrayList#add表示有一个新元素A的空间。紧接着其他线程将其元素放入列表中。现在AelementData[s] = e;没有空间抛出异常。