Vector、Hashtable 直接實例化Collections.synchronizedXxx() 靜態工廠方法包裝普通集合同步容器的每個單獨方法是線程安全的,但多方法組成的復合操作(如「先判斷大小再獲取最後元素」、「遍歷中修改」)並非原子,仍需外部加鎖。
Vector 索引越界// 線程 A:獲取最後元素 public static Integer getLast(Vectorlist) { int lastIndex = list.size() - 1; // ① 讀取大小 if (lastIndex < 0) return null; return list.get(lastIndex); // ② 獲取元素 } // 線程 B:刪除並添加元素 public static void deleteAndAdd(Vector list) { int lastIndex = list.size() - 1; if (lastIndex < 0) return; list.remove(lastIndex); // ③ 刪除導致大小變化 list.add(3); }
若 ① 執行後、② 執行前,線程 B 執行了 ③,導致容器縮小,② 時索引越界拋出 ArrayIndexOutOfBoundsException。
public static Integer getLast(Vectorlist) { synchronized (list) { // 必須鎖住容器對象本身 int lastIndex = list.size() - 1; if (lastIndex < 0) return null; return list.get(lastIndex); } }
同理,for 循環遍歷時若其他線程修改容器,也會拋異常,需同步整個遍歷塊,但會嚴重降低並發性。
同步容器迭代期間若被並發修改,拋出 ConcurrentModificationException(fail-fast)。解決途徑:
CopyOnWriteArrayList),適合「讀多寫少」場景注意:toString()、equals()、hashCode() 等隱式迭代操作同樣會觸發快速失敗。
同步容器「一把鎖鎖全局」嚴重限制吞吐量。JDK 5.0 引入並發容器,核心優勢:
ConcurrentModificationExceptionputIfAbsent、replace 等ConcurrentHashMap| 特性 | 說明 |
|---|---|
| 分段鎖技術 | 將哈希表數組分為多個段,每段獨立加鎖,理論上支持 N 個線程並發寫入(N=段數) |
| 弱一致性迭代器 | 迭代時反映「某個時間點」的狀態,不拋異常,允許並發修改 |
| size()/isEmpty() | 返回近似值,因統計過程中容器可能在變化 |
| 原子複合操作 | putIfAbsent(k,v)、remove(k,v)、replace(k,old,new) 等 |
CopyOnWriteArrayList/Set:寫時復制,遍歷極快,適合「讀多寫少、容忍短暫不一致」場景(如事件監聽器列表)ConcurrentSkipListMap/Set:跳表實現,有序並發映射/集合ConcurrentLinkedQueue:無鎖隊列,高吞吐生產消費| 場景 | 推薦容器 | 理由 |
|---|---|---|
| 高並發 Map 讀寫 | ConcurrentHashMap | 分段鎖 + 弱一致性迭代器,性能最優 |
| 頻繁遍歷、極少修改的 List | CopyOnWriteArrayList | 遍歷無鎖、無需同步,寫入代價大但頻率低 |
| 有序 Map 需並發 | ConcurrentSkipListMap | 跳表 O(log n) 操作,支持並發範圍查詢 |
| 簡單生產消費隊列 | ConcurrentLinkedQueue | 無鎖 CAS,吞吐極高 |
同步容器是「防禦性同步」,並發容器是「進攻性優化」。理解復合操作原子性、迭代器弱一致性、鎖粒度對吞吐的影響,是駕馭 Java 並發集合的關鍵。實戰中:優先選並發容器,避免客戶端加鎖,拥抱弱一致性。