Fail-Fast(快速失敗) 是 Java 集合框架中一種錯誤檢測機制:當多線程並發修改集合結構時,迭代器能快速拋出 ConcurrentModificationException,而非產生不可預期的行為。本文以 ArrayList 為例,從源碼級原理、觸發場景、解決方案到並發包替代實現全維度剖析。
add/remove/clear 等),立即拋出 ConcurrentModificationException。java.util 包下所有非線程安全集合(ArrayList、HashMap、LinkedList、Vector 等)。import java.util.*;
public class FailFastDemo {
static List<String> list = new ArrayList<>(); // 非線程安全
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 6; i++) {
list.add(String.valueOf(i));
printAll();
}
}, "Thread-A").start();
new Thread(() -> {
for (int i = 10; i < 16; i++) {
list.add(String.valueOf(i));
printAll();
}
}, "Thread-B").start();
}
static void printAll() {
Iterator<String> it = list.iterator();
while (it.hasNext()) System.out.print(it.next() + ", ");
System.out.println();
}
}
// 運行結果:拋出 java.util.ConcurrentModificationException
expectedModCount = modCount(假設為 N)。list.add() → modCount++ 變為 N+1。it.next() → checkForComodification() 發現 expectedModCount != modCount → 拋異常。// AbstractList.java
protected transient int modCount = 0; // 結構修改計數器
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 創建迭代器時快照
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public E next() {
checkForComodification(); // 每次 next() 前檢查
...
}
public void remove() {
checkForComodification(); // remove() 也檢查
...
expectedModCount = modCount; // 迭代器自身修改後同步快照
}
}
// ArrayList.java 片段
public boolean add(E e) {
ensureCapacity(size + 1); // 內部 modCount++
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
ensureCapacity(size + 1); // modCount++
System.arraycopy(...);
elementData[index] = element;
size++;
}
public E remove(int index) {
modCount++; // 直接修改
...
}
public void clear() {
modCount++;
...
}
結論:任何導致元素數量變化的結構性修改(add/remove/clear 等)都會 modCount++。
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) { // 隱式創建 Iterator
if ("b".equals(s)) list.remove(s); // 直接調用集合 remove → modCount++
}
// 拋 ConcurrentModificationException
正確做法:使用迭代器自身的 remove()。
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove(); // 迭代器 remove 會同步 expectedModCount
}
synchronized (list) {
for (String s : list) { ... }
}
缺點:遍歷期間鎖住整個集合,併發度極低。
List<String> copy = new ArrayList<>(list); // 複製快照
for (String s : copy) { ... }
| 非線程安全集合 | 並發包替代類 | 特性 |
|---|---|---|
| ArrayList | CopyOnWriteArrayList | 寫時復制,遍歷無鎖,適合讀多寫少 |
| HashMap | ConcurrentHashMap | CAS + synchronized 細粒度鎖,高併發首選 |
| LinkedList | ConcurrentLinkedDeque | 無鎖雙端隊列,適合高併發隊列/棧 |
| Vector/Stack | 同上 | Vector 自帶同步但粗粒度,建議遷移 |
// CopyOnWriteArrayList.java 核心
public Iterator<E> iterator() {
return new COWIterator<>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot; // 創建迭代器時拷貝當前數組快照
private int cursor;
COWIterator(Object[] elements, int initialCursor) {
this.snapshot = elements; // 關鍵:快照隔離
this.cursor = initialCursor;
}
public boolean hasNext() { return cursor < snapshot.length; }
public E next() { return (E) snapshot[cursor++]; }
public void remove() { throw new UnsupportedOperationException(); } // 迭代器不可修改
// 無 modCount、無 checkForComodification、無 ConcurrentModificationException
}
核心思想:讀寫分離。寫入時復制新數組(Arrays.copyOf),讀取遍歷舊數組快照。迭代器見到的是創建時刻的靜態視圖,自然無併發修改衝突。
Iterator.remove() / ListIterator.add/set/remove。java.util.concurrent 包下對應並發集合。CopyOnWriteArrayList / CopyOnWriteArraySet。ConcurrentHashMap。ConcurrentLinkedQueue / ConcurrentLinkedDeque / ArrayBlockingQueue。