概述
ArrayList 是線程不安全的集合類,當多線程環境下,並發對同一個ArrayList執行add,可能會拋出java.util.ConcurrentModificationException的異常
例子
這邊有個簡單的程序,創建30個線程,分別對ArrayList執行add操作
public class ListApp
{
public static void main( String[] args ) throws InterruptedException {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 30; i++){
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
};
}
}
輸出結果如下,確實報錯了
異常原因分析
首先,看一下 ArrayList 源碼,這里只貼出代碼關鍵的部分。里面的注釋是我自己添加的,這樣看起來更清晰一些。
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList的底層存儲就是個Object[]數組
transient Object[] elementData;
private int size;
private static final int DEFAULT_CAPACITY = 10;
public ArrayList() {
//構造函數將elementData初始化為{}空數組
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e) {
//檢查elementData數組大小,大小不夠就進行數組擴容。繼續跳到下一個函數
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果當前是空數組,就把數組大小初始化為10(DEFAULT_CAPACITY=10)
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//數組大小不夠存放新數據了,此時需要擴容
if (minCapacity - elementData.length > 0)
//grow()才是真正執行擴容的函數。繼續往下看
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//這里就不細講了,關鍵就是計算出newCapacity新的容器大小
//調用Arrays.copyOf進行復制,構造出一個新的數組
elementData = Arrays.copyOf(elementData, newCapacity);
}
異常原因總結
由此可見,ArrayList的所有方法都沒有加Lock,也沒有加synchronized,因此在並發操作下,擴容函數grow()會存在問題。
舉個簡單的例子:
- elementData數組剛剛添加了最后一個元素,也就是剛好滿員了
- 這時2個線程同時又調用了add,那么就必須要執行grow進行擴容
- 第1個線程調用完grow(),然后也調用了elementData[size++] = e,把新元素添加上去
- 第2個線程又調用一次grow(),整個elementData數組就亂掉了
問題解決
使用 Vector 初始化 list 對象
List<String> list = new Vector<>();
因為Vector.add使用了synchronized加鎖
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
使用 Collections.synchronizedList
List<String> list = Collections.synchronizedList(new ArrayList<>());
這種情況下,調用的是SynchronizedList.add,源碼如下,同樣做了加鎖
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
使用 CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
底層使用的是ReentrantLock,源碼如下:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}