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

線程安全遍歷 List 完全指南:Vector、CopyOnWriteArrayList、forEach 實戰對比

     編輯:Java 程式設計入門

五種遍歷方式速覽

方式代碼示例本質推薦度
索引 for 循環for(int i=0;i隨機訪問❌ 差(LinkedList O(n²))
Iterator 顯式Iterator it=list.iterator(); while(it.hasNext()) it.next()迭代器模式✅ 基礎
增強 forfor(Object item:list)Iterator 語法糖✅ 推薦
forEach 匿名類list.forEach(new Consumer(){...})內部迭代✅ 函數式
forEach Lambdalist.forEach(item -> ...)Lambda 語法糖⭐ 首選

並發修改異常實驗

List list = new ArrayList<>();
list.add(0); list.add(1); list.add(2); list.add(3); list.add(4);

// 線程1 遍歷
new Thread(() -> {
    for (int item : list) {
        System.out.println("遍歷: " + item);
        Thread.sleep(1000);
    }
}).start();

// 線程2 刪除
new Thread(() -> {
    Thread.sleep(1000);
    list.remove(4);
    System.out.println("list.remove(4)");
}).start();

輸出:

遍歷: 0
遍歷: 1
list.remove(4)
Exception in thread "Thread-0" java.util.ConcurrentModificationException

原因:ArrayList 迭代器檢測 modCount 變化,拋出 ConcurrentModificationException(快速失敗機制)。

Vector:線程安全 ≠ 遍歷安全

Vector 方法級 synchronized 保護單操作,但無法鎖定整個遍歷過程。直接遍歷同樣拋出 ConcurrentModificationException。

手動加鎖方案

// 線程1
synchronized (list) {
    for (int item : list) { ... }
}

// 線程2
list.remove(4); // Vector.remove 內部 synchronized,會等待鎖釋放

輸出:遍歷完成後才執行 remove,安全但鎖住整個列表,並發度極低。

CopyOnWriteArrayList:讀寫分離利器

核心原理

  • 寫操作(add/remove/set)複製底層數組,修改副本,最後 volatile 切換引用。
  • 讀操作(遍歷、get)直接訪問當前數組快照,無需加鎖。
  • 迭代器遍歷的是創建迭代器時的數組快照,不感知後續修改。

實驗驗證

List list = new CopyOnWriteArrayList<>();
// ... 初始化 0-4

// 線程1 遍歷
new Thread(() -> {
    for (int item : list) { ... }
}).start();

// 線程2 刪除
new Thread(() -> {
    Thread.sleep(1000);
    list.remove(4);
    System.out.println("list.remove(4)");
}).start();

輸出:

遍歷: 0
遍歷: 1
list.remove(4)
遍歷: 2
遍歷: 3
遍歷: 4

遍歷仍見到已刪除元素 4,因遍歷的是舊數組快照。最終一致性,適合「讀多寫少」場景。

List.forEach:內部迭代的線程安全性

Vector.forEach 源碼

public synchronized void forEach(Consumer action) { ... }

方法級 synchronized 鎖定整個遍歷過程,線程安全遍歷。

實驗驗證

List list = new Vector<>();
// ... 初始化

new Thread(() -> {
    list.forEach(item -> { ... }); // 內部加鎖
}).start();

new Thread(() -> {
    Thread.sleep(1000);
    list.remove(4);
}).start();

輸出:遍歷全部完成後執行 remove,安全且代碼簡潔。

選型決策表

場景推薦方案優點缺點
讀多寫少、容忍弱一致性CopyOnWriteArrayList無鎖讀、高並發讀性能寫入複製數組開銷大、內存佔用高
寫入頻繁、需強一致性Vector + 手動 synchronized / Collections.synchronizedList強一致、標準 API遍歷期間鎖住整表、並發度低
JDK 8+、函數式風格、需安全遍歷Vector.forEach / CopyOnWriteArrayList.forEach代碼簡潔、內部鎖控制Vector.forEach 仍鎖全表
高性能並發寫入避免 List,改用 ConcurrentHashMap 或 隊列分段鎖/CAS 高吞吐語義不同,需重新設計

最佳實踐總結

  1. 拒絕索引 for 循環遍歷 LinkedList。
  2. 單線程首選 forEach Lambda 或增強 for。
  3. 多線程讀多寫少 → CopyOnWriteArrayList(接受遍歷見舊數據)。
  4. 多線程強一致性 → Vector.forEach 或手動 synchronized(list) 包裹遍歷。
  5. 高並發寫入場景重新評估數據結構選型。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved