ArrayList从源码上看其线程安全问题(jdk1.8),arraylistjdk1.8
分享于 点击 42298 次 点评:42
ArrayList从源码上看其线程安全问题(jdk1.8),arraylistjdk1.8
ArrayList是线程不安全的。我们来看他的add方法:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 传入size+1
elementData[size++] = e; //赋值,size++
return true;
}
首先调用ensureCapacityInternal方法,顾名思义:首先要确保容量,不然怎么add。我们看这个方法:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
第一个if语句是用于初次add元素的扩容的,将会扩容到10.(这一部分可以看上一篇)
看接下来的方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++; //fast-fail
//如果目前的容量足够的话,是不会扩容的
if (minCapacity - elementData.length > 0)
//如果传入的size+1 > 目前的数组长度了,那么进行扩容操作
grow(minCapacity);
}
grow方法,就不细述了,是通过数组复制的办法进行的,扩了1.5倍。回到最初的add方法,接下来执行的句子:
elementData[size++] = e;
拆成2句:elementData[size] = e; //1
size++; //2
这里就可以看出端倪了:假定2个线程A,B同时add一个元素,A执行到第一句,此时cpu给了B,此时size并没有加1,所以线程B所add的值就覆盖了线程A所add的,接下来size++,加了2次,elementData[size+1]就变成了null。
还有一种情况:会直接抛出异常,我们假定一个场景,此时arraylist中数组容量为4,size为3,就是还有一个空位置,此时线程A,B同时add一个元素,A执行完ensureCapacityInternal(size + 1),因为还没有到需要扩容的条件,是不会扩容的,此时容量还是4,接着B拿到CPU,也执行ensureCapacityInternal(size + 1),当然也不会执行扩容,接着继续执行,elementData[size] = e; size++; 接着cpu给了A,异常发生,此时size=4了,容量也=4,然而线程A已经执行过ensureCapacityInternal这个方法了,直接调用elementData[size] = e也就是elementData[4] = e,那么肯定就抛出异常了(数组越界)。
写一个最简单的例子:
public class ArrayListThreadSample {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
new Thread(()->{
for (int i=0; i<1000; i++) {
list.add(i);
}
}).start();
new Thread(()->{
for (int i=0; i<1000; i++) {
list.add(i);
}
}).start();
System.out.println(list.size());
list.forEach(o-> System.out.print(o+" "));
}
}
输出结果可能直接抛异常,可能输出的size<500,当然也有可能输出正常。相关文章
- 暂无相关文章
用户点评