您的位置:首页 > 编程语言 > Java开发

ArrayList在多线程调用Add()添加元素时的下标越界问题(java.lang.ArrayIndexOutOfBoundsException)

2016-04-27 10:24 483 查看
最近在看《实战Java虚拟机》一书,看到有关锁与并发章节时,看到如下一个多线程使用ArrayList的例子:





两个线程t1和t2同时向numberList中添加数据,由于ArrayList是线程不安全的,因此会导致添加的数据有错误,这个我还是能理解的,但是它报的确是如下错误:



我就有点理解不了了,ArrayList不是自动扩容、没有长度限制吗,为什么还会出现数组下标越界这种错误呢?

为了便于分析,我对代码进行了一点点修改:



执行结果为:













有时还会出现null,



带着种种不解,来看ArrayList添加流程:

首先,ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。

1、程序中报错的atjava.util.ArrayList.elementData(ArrayList.java:400)和atjava.util.ArrayList.add(ArrayList.java:441),它们同属Add()方法。

源码如下:



添加操作,首先会调用ensureCapacityInternal(size
+1),其作用为保证数组的容量始终够用,其中size是elementData数组中元组的个数,初始为0。



在ensureCapacityInternal()函数中,用if判断,如果数组没有元素,给数组一个默认大小,会选择实例化时的值与默认大小中较大值,然后调用ensure
Explicit
Capacity()。



函数体中,modCount是数组发生size更改的次数。然后if判断,
如果数组长度小于默认的容量10,则调用扩大数组大小的方法grow()。[/code]

函数grow()解释了基于数组的ArrayList是如何扩容的。数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。

接下来回到Add()函数,继续执行,elementData[size++]=e;这行代码就是问题所在,当添加一个元素的时候,它可能会有两步来完成:1.在elementData[Size]的位置存放此元素;2.增大Size的值。

在单线程运行的情况下,如果Size=0,添加一个元素后,此元素在位置0,而且Size=1;

而如果是在多线程情况下,比如有两个线程,线程A先将元素存放在位置0。但是此时CPU调度线程A暂停,线程B得到运行的机会。线程B也向此ArrayList添加元素,因为此时Size仍然等于0(注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加Size的值。那好,我们来看看ArrayList的情况,元素实际上只有一个,存放在位置0,而Size却等于2。这就是“线程不安全”了。这就解释了为何集合中会出现null。

但是数组下标越界还不能仅仅依靠这个来解释。我们观察发生越界时的数组下标,分别为10、15、22、33、49和73。结合前面讲的数组自动机制,数组初始长度为10,第一次扩容为15=10+10/2,第二次扩容22=15+15/2,第三次扩容33=22+22/2...以此类推,我们不难发现,越界异常都发生在数组扩容之时。

由此给了我想法,我猜想是,由于没有该方法没有同步,导致出现这样一种现象,用第一次异常,即下标为15时的异常举例。当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size+1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size+1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。而刚刚阻塞在elementData[size++]=e;语句之前的线程开始执行,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!




                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: