【问题标题】:Multi Threading with ArrayList() got ArrayIndexOutOfBoundsException [duplicate]使用 ArrayList() 的多线程得到 ArrayIndexOutOfBoundsException [重复]
【发布时间】:2018-08-25 15:12:59
【问题描述】:

我编写了以下代码来通过非线程安全对象(这里是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

谁能向我解释导致此特定错误的原因?

【问题讨论】:

  • 你在哪里创建线程?告诉我你创建和启动线程的主要方法?
  • “谁能向我解释导致此错误的原因” - 是的,您正在以文档告诉您不要的方式使用该类型。当坏事发生在那个时候,你不应该感到惊讶。
  • 您已将 List 声明为静态,因此该列表对 A 类的每个实例都是通用的。因此您的所有线程同时访问它。所以它崩溃了
  • @RavindraRanwala 在主课
  • 主要是 因为add 会在数据无法放入已经存在的数组时在内部进行调整大小,因此一个线程尝试增加该数组,而另一个线程尝试放入其中(索引不存在)

标签: java multithreading arraylist concurrency


【解决方案1】:

ArrayList 不是线程安全类。

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

您添加元素的速度太快,因此ArrayList#add() -&gt; grow() -&gt; newCapacity() 无法计算正确的容量来为所有进入的元素分配内存。

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

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

【讨论】:

    【解决方案2】:

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

    让我们检查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个新元素,但随后多个线程尝试同时添加一个元素,所以elementData[size++]为其中一些抛出ArrayIndexOutOfBoundsException

    【讨论】:

    • +1 这是完全正确的,只是补充一点,这是可能发生的最不可能发生的意外,还有更多,更糟糕的是,比如过时的数据、不完整的 ArrayList 等
    猜你喜欢
    • 2016-08-31
    • 2014-08-31
    • 2011-10-05
    • 2018-06-10
    • 2011-11-30
    • 1970-01-01
    • 2015-11-08
    • 2014-11-17
    • 2013-05-20
    相关资源
    最近更新 更多