我们先来看看 ArrayList 的 add 操作源码。
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
ArrayList 的不安全主要体现在两个方面。
其一:
elementData[size++] = e;
不是一个原子操作,是分两步执行的。
elementData[size] = e;
size++;
单线程执行这段代码完全没问题,可是到多线程环境下可能就有问题了。可能一个线程会覆盖另一个线程的值。
列表为空 size = 0。
线程 A 执行完 elementData[size] = e;之后挂起。A 把 “a” 放在了下标为 0 的位置。此时 size = 0。
线程 B 执行 elementData[size] = e; 因为此时 size = 0,所以 B 把 “b” 放在了下标为 0 的位置,于是刚好把 A 的数据给覆盖掉了。
线程 B 将 size 的值增加为 1。
线程 A 将 size 的值增加为 2。
这样子,当线程 A 和线程 B 都执行完之后理想情况下应该是 “a” 在下标为 0 的位置,“b” 在标为 1 的位置。而实际情况确是下标为 0 的位置为 “b”,下标为 1 的位置啥也没有。
其二:
ArrayList 默认数组大小为 10。假设现在已经添加进去 9 个元素了,size = 9。
线程 A 执行完 add 函数中的ensureCapacityInternal(size + 1)挂起了。
线程 B 开始执行,校验数组容量发现不需要扩容。于是把 “b” 放在了下标为 9 的位置,且 size 自增 1。此时 size = 10。
线程 A 接着执行,尝试把 “a” 放在下标为 10 的位置,因为 size = 10。但因为数组还没有扩容,最大的下标才为 9,所以会抛出数组越界异常 ArrayIndexOutOfBoundsException