首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 集合進階:Fail-Fast 快速失效機制原理與 CopyOnWriteArrayList 並發解決方案

     編輯:Java 綜合教學

Fail-Fast 核心機制

非同步併發修改檢測機制。集合內部維護 modCount(修改次數),迭代器建立時快照 expectedModCount = modCount。每次 next() 前比對,若不一致拋 ConcurrentModificationException(CME)。

ArrayList 原始碼證據

// ArrayList.java
private transient int modCount = 0;

// Itr 迭代器
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

觸發場景實測

// 單執行緒也會觸發:迭代過程中結構性修改
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));
Iterator<Integer> it = list.iterator();
list.add(4); // 結構性修改
it.next(); // 拋 CME

// 多執行緒:寫執行緒插入,讀執行緒遍歷
List<Integer> list = new ArrayList<>();
new Thread(() -> { while(true) { list.add(1); sleep(500); } }).start();
Iterator<Integer> it = list.iterator();
while (it.hasNext()) { it.next(); sleep(80); } // 幾乎必現 CME

解決方案對比

方案原理優點缺點適用場景
加鎖同步synchronized(list) 包裹遍歷簡單、強一致性鎖住整個遍歷期,併發度極低讀寫極不平衡、遍歷極短
Collections.synchronizedList裝飾器模式,方法級鎖API 兼容迭代器仍需手動同步,易忘輕量級同步
CopyOnWriteArrayList寫時複製新陣列,讀舊陣列讀完全無鎖、遍歷不拋 CME、適合讀多寫少寫入開銷大(複製陣列)、記憶體雙倍、資料最終一致性快取、配置、事件監聽器、黑白名單

CopyOnWriteArrayList 底層原理解析

寫入操作:複製-修改-替換

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); // volatile 寫入,發布新陣列引用
        return true;
    } finally { lock.unlock(); }
}

讀取/迭代:快照隔離

// 迭代器構造器捕獲當前陣列引用
private static class COWIterator<E> implements ListIterator<E> {
    private final Object[] snapshot; // 關鍵:不可變快照
    private int cursor;
    COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }
    public E next() { return (E) snapshot[cursor++]; }
    public boolean hasNext() { return cursor < snapshot.length; }
}

核心特性:

  • 迭代器遍歷的是建立迭代器時刻的陣列快照,後續寫入不影響當前遍歷。
  • 不支援 remove()/set()/add()(拋 UnsupportedOperationException)。
  • getArray() 回傳當前最新陣列,get(index) 讀最新值。

效能實測:CopyOnWriteArrayList vs 同步 ArrayList

// 1000 元素,10 萬次讀(遍歷),100 次寫(add)
CopyOnWriteArrayList: 讀 12ms / 寫 45ms
synchronized ArrayList: 讀 180ms / 寫 220ms (鎖爭用)
ArrayList (無同步): 讀 8ms / 寫 5ms (非線程安全)

讀多寫少(> 99% 讀)場景,COW 完勝。

進階避坑指南

  1. 記憶體尖峰:大列表(> 10 萬元素)頻繁寫入會導致頻繁 Full GC,考慮分片或換 ConcurrentHashMap 方案。
  2. 最終一致性陷阱:執行緒 A 寫入後,執行緒 B 立即遍歷可能看不到新元素(遍歷的是舊快照)。業務若需強一致,勿用 COW。
  3. 複合操作非原子:if (!list.contains(x)) list.add(x) 非原子,仍需外層鎖。
  4. 替代方案:JDK 8+ StampedLock 優化讀寫鎖、ConcurrentHashMap 做鍵值化存儲、Guava ImmutableList 靜態配置。

選型決策表

場景推薦集合理由
單執行緒、高性能ArrayList零開銷
多執行緒、讀寫均衡ConcurrentHashMap / Collections.synchronizedList細粒度鎖 / 簡單同步
多執行緒、讀多寫少、遍歷頻繁CopyOnWriteArrayList讀無鎖、遍歷安全
佇列/生產消費ConcurrentLinkedQueue / ArrayBlockingQueue專用佇列語義
不可變配置ImmutableList / List.of()編譯期/執行期保證不可變
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved