ArrayList为什么不是线程安全的
线程安全的问题,只有在涉及到多线程的时候才会出现,在单线程的环境中,是不会出现线程安全的问题。常用的集合类型ArrayList为什么不是线程安全的呢? 就是因为他的add方法,先看源码
// 添加元素方法
public boolean add(E e) {
// 判断是否需要扩容,size+1比当前集合长度大则扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果elementData为空,即长度为0时,返回10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// 如果size+1比当前的elementData的长度大,即这次插入数据后就达到最大容量,则需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 扩容后大小为原来的1.5倍,>>1为二进制形式向右移动一位,即除以2
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
// 创建一个新的数组并把数据复制到新数组,同时elementData指向新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
那么线程安全的问题出在了哪里呢,有两个原因: 一:数据插入时覆盖 因为elementData[size++] = e;并不是一个原子操作,或者说size++并不是一个原子操作,而是分为两步 elementData[size] = e; size = size+1; 先读取size,再+1,那就会又可能出现资源竞争 现在size为1,A线程与B线程同时进行插入操作,A线程往下标为1的位置插入A,elementData[1] = A;还没等A线程执行size = size + 1;B线程就又执行赋值操作了,此时size还是为1,所以elementData[1] = B;那么最后elementData[1] = B。 二:数据插入时数组越界 具体逻辑如下: 数组长度为默认的10,size为9,此时A线程插入A,B线程插入B,两者都在调用ensureCapacityInternal方法进行容量判断,都不需要进行扩容,A线程插入成功,size = 10,此时B线程再插入,下标为10,但是因为没有扩容,已经超过数组的长度,所以会报出一个数组越界的异常ArrayIndexOutOfBoundsException。
解决方案:使用线程安全的list,如:Vector,SynchronizedList,SynchronizedList。 Vector,SynchronizedList都是全部方法加上同步对象锁,不推荐 SynchronizedList适用于读多写少的场景,读取时不加锁,只有在写入时才会加锁,再进行复制替换操作。
