hello啊,各位觀眾姥爺們!!!本baby今天來報道了!哈哈哈哈哈嗝🐶
面試官:詳細說說CopyOnWriteArrayList
CopyOnWriteArrayList 詳解
CopyOnWriteArrayList 是 Java 并發包(java.util.concurrent
)中提供的線程安全列表,基于“寫時復制”(Copy-On-Write)機制實現。它適用于讀多寫少的高并發場景,如事件監聽器列表、配置管理等。
核心特性
特性 | 說明 |
---|---|
線程安全 | 讀操作無鎖,寫操作通過鎖保證線程安全。 |
數據一致性 | 讀操作基于快照,迭代期間不會拋出 ConcurrentModificationException 。 |
寫開銷 | 每次修改操作會復制底層數組,內存占用較高。 |
適用場景 | 讀操作頻繁,寫操作極少(如監聽器管理、配置存儲)。 |
底層實現
-
數據結構
- 基于動態數組(
volatile Object[] array
)存儲數據。 - 所有讀操作直接訪問當前數組,無需同步。
- 基于動態數組(
-
寫時復制(COW)
- 修改操作流程:
- 加鎖(
ReentrantLock
)。 - 復制原數組,生成新數組。
- 在新數組上執行修改操作。
- 將底層數組引用指向新數組(
setArray(newArray)
)。 - 釋放鎖。
- 加鎖(
- 代碼示例(
add
方法):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();} }
- 修改操作流程:
-
迭代器
- 基于迭代器創建時的數組快照遍歷數據。
- 不支持修改操作(如
remove
、set
),調用會拋出UnsupportedOperationException
。 - 代碼示例:
public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0); }
優點與缺點
優點 | 缺點 |
---|---|
讀操作無鎖,性能極高。 | 寫操作內存開銷大(復制全量數據)。 |
避免并發修改異常(ConcurrentModificationException )。 | 數據弱一致性(讀操作可能不反映最新狀態)。 |
實現簡單,適合讀多寫少場景。 | 寫操作頻繁時性能急劇下降。 |
適用場景
-
監聽器列表:
如 GUI 事件監聽器,注冊后極少修改,但頻繁觸發事件(讀)。// 添加監聽器(寫操作少) listeners.add(new Listener());// 觸發事件(讀操作多) for (Listener listener : listeners) {listener.onEvent(); }
-
配置管理:
系統配置通常加載后很少修改,但頻繁讀取。CopyOnWriteArrayList<Config> configs = loadConfigs(); // 讀取配置(無鎖) String value = configs.get(0).getProperty("key");
-
緩存快照:
需要緩存某個時間點的數據快照供查詢。
性能對比
操作 | ArrayList | CopyOnWriteArrayList | Collections.synchronizedList |
---|---|---|---|
讀(單線程) | O(1)(最快) | O(1)(無鎖,快) | O(1)(同步開銷,較慢) |
讀(高并發) | 非線程安全,需外部同步 | O(1)(無鎖,最快) | O(1)(同步開銷,慢) |
寫(單線程) | O(1)(快) | O(n)(復制數組,慢) | O(1)(同步開銷,較慢) |
寫(高并發) | 非線程安全 | O(n)(鎖競爭,最慢) | O(1)(同步開銷,慢) |
注意事項
-
避免頻繁寫操作:
批量寫入時,優先使用addAll
代替多次add
,減少數組復制次數。// 不推薦 for (String item : items) {list.add(item); }// 推薦 list.addAll(items);
-
迭代器弱一致性:
迭代器遍歷的是創建時的快照,可能無法感知后續修改。CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3)); Iterator<Integer> it = list.iterator(); list.add(4); while (it.hasNext()) {System.out.print(it.next()); // 輸出 1,2,3(不包含4) }
-
內存監控:
大對象或超大數組可能導致內存壓力,需監控堆內存使用。
🐮🐎
- 使用場景:讀多寫少,允許數據弱一致性。
- 替代方案:
- 寫多讀少:考慮
ConcurrentLinkedQueue
或ConcurrentHashMap
。 - 強一致性需求:使用鎖或
synchronizedList
。
- 寫多讀少:考慮
- 最佳實踐:結合業務特點選擇數據結構,必要時進行性能壓測。