ArrayList多线程调用add()可能出现的问题
一 ArrayList在多线程调用Add()添加元素时的下标越界问题
下标越界问题 java.lang.ArrayIndexOutOfBoundsException
public class ArrayListTest {
public static List<Integer> list = new ArrayList<>();
static class AddToList implements Runnable {
int num = 0;
public AddToList(int num) {
this.num = num;
}
@Override
public void run() {
int count = 0;
while (count < 100) {
list.add(num);
num += 1;
count++;
}
}
}
public static void main(String[] args) {
new Thread(new AddToList(0)).start();
new Thread(new AddToList(1)).start();
}
}
同时使用两个线程进行添加数据,很容易会出现如下异常:
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 73
at java.util.ArrayList.add(ArrayList.java:459)
at com.algo.hongbao.ArrayListTest$AddToList.run(ArrayListTest.java:21)
at java.lang.Thread.run(Thread.java:748)
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长查看ArrayList.add(E e) 方法的添加流程:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
-
首先会调用ensureCapacityInternal(size + 1),其作用为保证数组的容量始终够用,其中size是elementData数组中元组的个数,初始为0。
-
在ensureCapacityInternal()函数中,用if判断,如果数组没有元素,给数组一个默认大小,会选择实例化时的值与默认大小中较大值,然后调用ensureExplicitCapacity。
-
函数体中,modCount是数组发生size更改的次数。然后if判断,如果数组长度小于默认的容量10,则调用扩大数组大小的方法grow()。
-
函数grow()解释了基于数组的ArrayList是如何扩容的。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。
-
回到Add()函数,继续执行,elementData[size++] = e;
根据上面执行流程,其实我们很容器发现问题的所在,其实就是最后这一行代码elementData[size++] = e;导致。添加一个元素我们可以理解为:
- 在 elementData[Size] 的位置存放此元素;
- 增大 Size 的值。
用第一次下标为15时的异常举例。当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size,变为15,数组已经满了。而刚刚阻塞在elementData[size] = e;语句之前的线程开始执行,它需要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!
多线程并发导致List的add()失败,元素为null
执行add方法时,会先将此容器的大小增加。。即size++,然后将传进的元素赋值给新增的elementData[size++],即新的内存空间
但是此时如果在size++后直接来取这个List,而没有让add完成赋值操作,则会导致此List的长度加一,,但是最后一个元素是空(null),所以在获取它进行计算的时候报了空指针异常。
网友评论