問題背景
在多線程編程中,我們經常會遇到集合類的線程安全問題。Java中的ArrayList
是一個常用的集合類,但它不是線程安全的。當多個線程同時操作同一個ArrayList
實例時,可能會出現各種不可預料的問題。
問題演示
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(list);}, String.valueOf(i)).start();
}
運行上述代碼,很可能會遇到以下異常:
ArrayIndexOutOfBoundsException
:數組越界異常ConcurrentModificationException
:并發修改異常或者出現數據不一致的情況
為什么ArrayList線程不安全?
ArrayList
的線程不安全主要體現在以下幾個方面:
add方法非原子操作:
add()
操作涉及多個步驟(檢查容量、擴容、賦值),在多線程環境下可能被中斷modCount計數器:迭代過程中如果結構被修改,會拋出
ConcurrentModificationException
可見性問題:一個線程的修改可能不會立即對其他線程可見
解決方案
方案一:使用Vector類
List<String> list = new Vector<>();
Vector
是Java早期提供的線程安全集合類,通過在方法上添加synchronized
關鍵字實現同步。
優點:
簡單易用,直接替換即可
保證強一致性
缺點:
性能較差,所有操作都需要獲取鎖
過于保守的同步策略
方案二:使用Collections.synchronizedList()
List<String> list = Collections.synchronizedList(new ArrayList<>());
這種方法返回一個同步包裝器,將所有方法用synchronized
塊包裝。
優點:
靈活性高,可以包裝任意List實現
與Vector類似的線程安全性
缺點:
性能仍然有損耗
迭代時需要手動同步
// 迭代時需要額外同步
synchronized(list) {Iterator<String> it = list.iterator();while (it.hasNext()) {// 處理元素}
}
方案三:使用CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList
是JUC包中提供的線程安全集合,采用"寫時復制"策略。
工作原理:
讀操作:無鎖,直接訪問當前數組
寫操作:加鎖,復制原數組,在新數組上修改,最后替換引用
優點:
讀操作性能極高,適合讀多寫少的場景
不會拋出
ConcurrentModificationException
缺點:
寫操作性能較差,需要復制整個數組
內存占用較大
數據弱一致性(讀操作可能看不到最新的修改)
性能對比
實現方式 | 讀性能 | 寫性能 | 一致性 | 內存占用 |
---|---|---|---|---|
ArrayList | 高 | 高 | 無保證 | 低 |
Vector | 低 | 低 | 強一致 | 低 |
Collections.synchronizedList | 低 | 低 | 強一致 | 低 |
CopyOnWriteArrayList | 極高 | 低 | 弱一致 | 高 |
實際應用建議
讀多寫少的場景:優先考慮
CopyOnWriteArrayList
寫多讀少的場景:考慮使用
Collections.synchronizedList()
或Vector
高并發且需要高性能的場景:考慮使用并發容器如
ConcurrentHashMap
(對應List可以考慮分段鎖實現)單線程環境:直接使用
ArrayList
即可
總結
ArrayList
的線程安全問題在多線程環境下必須重視。根據實際應用場景選擇合適的線程安全方案至關重要:
需要強一致性且不關心性能:
Vector
或Collections.synchronizedList()
讀多寫少且可以接受弱一致性:
CopyOnWriteArrayList
高性能要求:考慮自定義同步策略或使用更專業的并發容器
正確選擇線程安全集合類,可以有效避免多線程環境下的各種詭異問題,提高程序的穩定性和性能。
注意:即使使用了線程安全的集合類,復合操作(如檢查再添加)仍然可能需要額外的同步措施,這點需要特別注意。