自己通过学习ArrayList的源码,谈一下自己的感悟,不足之处,望多加指出,运维出身自学Java。
ArrayList源码中的属性
//默认容量
private static final int DEFAULT_CAPACITY = 10;
//空数组,如果传入的容量为0时使用
private static final Object[] EMPTY_ELEMENTDATA = {};
//空数组,传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储元素的数组
transient Object[] elementData;
//集合中元素的个数
private int size;
add操作的源码
public boolean add(E e) {
//检查是否需要扩容
ensureCapacityInternal(size + 1);
//把元素插到最后一位
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//新容量为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新容量发现比需要的容量还小,则以需要容量为准
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新容量已经超过最大容量了,则使用最大容量
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//以新容量拷贝出来一个新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
add操作大概分为两步:
一、判断列表的capacity容量是否足够,是否需要扩容;
二、将元素放在列表的元素数组里
第一个线程不安全的地方
假设当前集合中的元素个数为9,即size=9
线程A开始调用add方法,这是它获得到size的值为9,调用ensureCapacityInternal方法进行容量判断。
同是线程B也开始调用add方法,它获取到的size值也为9,也开始调用ensureCapacityInternal方法。
假设线程A需求为添加一个元素,size大小为9+1=10,而elementData的大小默认为10,可以容纳。于是它不再扩容,返回。
假设线程B需求也是添加一个元素,size大小为9+1=10,也可以容纳,返回。
线程A开始进行设置值操作,执行elementData[size++]=e后,size变10。
线程B同是也开始进行设置值操作,此时size的大小为10,但是线程B已经判断过了elementData大小可以容纳,不需要扩容,执行elementData[size++],即elementData[10++]=e。此时会报一个数组越界的异常ArrayIndexOutOfBoundsException。
第二个线程不安全的地方
另外elementData[size++]=e设置值的操作也会导致线程不安全。因为这步操作不是一个原子操作,它由以下两步构成:
1、elementData[size]=e;
2、size = size+1;
假设elementData当前size大小为0。
线程A添加一个元素“A”,将“A”放在elementData下标为0的位置上。
接着线程B刚好也要添加一个元素"B",且走到了第一步操作。此时线程B获取的size值依然是0,于是线程B将“B”也放在了elementData下标为0的位置上。
然后,线程A将size的值增加为1;线程B将size的值增加为2。
这样线程A、B执行完成后,elementData的期待结果为:size为2,elementData[0]=“A”,elementData[1]=“B”。
而实际情况是:size为2,elementData[0]=“B”,elementData[1]=null。