Java常用集合與映射的線程安全問題小結(jié)
Java常用集合與映射的線程安全問題深度解析
一、線程安全基礎(chǔ)認(rèn)知
在并發(fā)編程環(huán)境下,當(dāng)多個線程同時操作同一集合對象時,若未采取同步措施,可能導(dǎo)致以下典型問題:
- 數(shù)據(jù)競爭:多個線程同時修改數(shù)據(jù)導(dǎo)致結(jié)果不可預(yù)測
- 狀態(tài)不一致:部分線程看到集合的中間狀態(tài)
- 內(nèi)存可見性:線程本地緩存與主內(nèi)存數(shù)據(jù)不同步
- 死循環(huán)風(fēng)險:特定操作引發(fā)無限循環(huán)(如JDK7的HashMap擴容)
二、典型非線程安全集合問題分析
1. ArrayList的并發(fā)陷阱
// 錯誤示例 List<Integer> list = new ArrayList<>(); ExecutorService pool = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { pool.execute(() -> list.add(new Random().nextInt())); } // 運行結(jié)果可能包含:元素丟失、size值異常、數(shù)組越界異常等
問題根源:
add()
方法非原子操作:elementData[size++] = e
- 多線程同時觸發(fā)擴容導(dǎo)致數(shù)組拷貝覆蓋
- size變量可見性問題
2. HashMap的并發(fā)災(zāi)難
Map<String, Integer> map = new HashMap<>(); // 并發(fā)執(zhí)行put操作可能導(dǎo)致: // 1. JDK7及之前版本:環(huán)形鏈表導(dǎo)致CPU 100% // 2. JDK8+版本:數(shù)據(jù)丟失或size計數(shù)錯誤 // 3. 迭代時ConcurrentModificationException
底層機制:
- 哈希桶結(jié)構(gòu)在擴容時產(chǎn)生鏈表斷裂
- 頭插法(JDK7)與尾插法(JDK8)差異
- 沒有同步機制的Entry數(shù)組操作
3. HashSet的隱藏風(fēng)險
Set<Integer> set = new HashSet<>(); // 本質(zhì)是HashMap的包裝類,所有線程安全問題與HashMap一致 // add()方法并發(fā)調(diào)用時可能產(chǎn)生元素丟失
三、線程安全解決方案對比
1. 同步包裝方案
// 使用Collections工具類 List<String> syncList = Collections.synchronizedList(new ArrayList<>()); Map<String, Object> syncMap = Collections.synchronizedMap(new HashMap<>()); // 特征: // 1. 所有方法使用synchronized同步塊 // 2. 迭代器需要手動同步 // 3. 鎖粒度大,性能較差
2. 傳統(tǒng)線程安全集合
// Vector/Hashtable方案 Vector<String> vector = new Vector<>(); Hashtable<String, Integer> table = new Hashtable<>(); // 缺點: // 1. 全表鎖導(dǎo)致吞吐量低 // 2. 已逐漸被并發(fā)容器取代
3. 現(xiàn)代并發(fā)容器(java.util.concurrent包)
3.1 CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>(); // 實現(xiàn)原理: // 1. 寫操作時復(fù)制新數(shù)組 // 2. 最終一致性保證 // 適用場景:讀多寫少(如白名單配置)
3.2 ConcurrentHashMap
Map<String, Object> concurrentMap = new ConcurrentHashMap<>(); // JDK8+實現(xiàn)特點: // 1. 分段鎖升級為CAS+synchronized // 2. 節(jié)點鎖粒度(鎖單個哈希桶) // 3. 支持并發(fā)度設(shè)置
3.3 ConcurrentSkipListMap
NavigableMap<String, Integer> skipMap = new ConcurrentSkipListMap<>(); // 特征: // 1. 基于跳表實現(xiàn)的有序Map // 2. 無鎖讀取,寫入使用CAS
四、并發(fā)容器實現(xiàn)原理剖析
1. CopyOnWriteArrayList寫時復(fù)制機制
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(); } }
2. ConcurrentHashMap并發(fā)控制
JDK8關(guān)鍵實現(xiàn):
- 哈希桶數(shù)組+鏈表/紅黑樹
- CAS操作實現(xiàn)無鎖化讀取
- synchronized鎖單個節(jié)點
- size計算采用LongAdder機制
3. 并發(fā)隊列實現(xiàn)對比
隊列類型 | 鎖機制 | 適用場景 |
---|---|---|
ConcurrentLinkedQueue | CAS無鎖 | 高并發(fā)生產(chǎn)者消費者模式 |
LinkedBlockingQueue | ReentrantLock雙鎖 | 有界阻塞隊列 |
ArrayBlockingQueue | 單ReentrantLock | 固定容量隊列 |
五、最佳實踐與注意事項
1. 選型決策指南
- 讀多寫少:CopyOnWrite系列
- 高并發(fā)寫入:ConcurrentHashMap
- 強一致性需求:同步包裝類+手動鎖
- 有序性要求:ConcurrentSkipListMap
2. 常見誤區(qū)規(guī)避
- 錯誤認(rèn)知:認(rèn)為
Collections.synchronizedXXX
比并發(fā)容器更安全 - 迭代器問題:未對同步集合的迭代器加鎖
- 復(fù)合操作漏洞:即使使用線程安全集合,多個操作仍需同步
// 錯誤示例:即使使用ConcurrentHashMap仍需同步 if (!map.containsKey(key)) { map.put(key, value); // 非原子操作 } // 正確寫法: map.putIfAbsent(key, value);
3. 性能優(yōu)化建議
- 預(yù)估ConcurrentHashMap初始容量減少擴容
- 避免在CopyOnWriteArrayList中使用超大數(shù)組
- 合理設(shè)置并發(fā)級別(ConcurrentHashMap構(gòu)造函數(shù))
- 使用批量操作方法(如putAll)
六、高級話題擴展
1. 弱一致性迭代器
- ConcurrentHashMap的迭代器反映創(chuàng)建時的狀態(tài)
- 不保證迭代過程中數(shù)據(jù)變化可見
2. 原子復(fù)合操作
// 使用merge方法實現(xiàn)原子計數(shù) ConcurrentHashMap<String, Long> counterMap = new ConcurrentHashMap<>(); counterMap.merge("key", 1L, Long::sum);
3. 分段鎖的演進
- JDK7的Segment分段鎖(默認(rèn)16段)
- JDK8的Node粒度鎖(鎖單個哈希桶)
總結(jié)與建議
- 嚴(yán)格區(qū)分場景:根據(jù)讀寫比例、一致性要求選擇容器
- 理解實現(xiàn)原理:避免誤用并發(fā)容器特性
- 組合使用鎖機制:必要時搭配ReentrantLock使用
- 監(jiān)控工具輔助:使用JConsole觀察容器爭用情況
開發(fā)者應(yīng)當(dāng)建立以下意識:
- 沒有絕對線程安全的容器,只有相對安全的操作方式
- 并發(fā)問題往往在高壓場景下暴露
- 充分測試是驗證線程安全性的必要手段
通過合理選擇并發(fā)容器并遵循最佳實踐,可以顯著降低多線程環(huán)境下的集合操作風(fēng)險,構(gòu)建高性能高可靠的Java應(yīng)用系統(tǒng)。
到此這篇關(guān)于Java常用集合與映射的線程安全的文章就介紹到這了,更多相關(guān)java集合與映射內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring @Primary作用和實現(xiàn)原理詳解
今天分享一下Spring中的@Primary注解,Primary的意思是主要的,我們在使用spring的時候,難免會定義多個類型相同的bean,這時候如果不采取一些方法,那么是無法正常使用bean的,所以本就給大家介紹Spring @Primary的作用和實現(xiàn)原理2023-07-07Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解
這篇文章主要介紹了Java多線程之線程通信生產(chǎn)者消費者模式及等待喚醒機制代碼詳解,具有一定參考價值,需要的朋友可以了解下。2017-10-10基于SpringBoot + Android實現(xiàn)登錄功能
在移動互聯(lián)網(wǎng)的今天,許多應(yīng)用需要通過移動端實現(xiàn)與服務(wù)器的交互功能,其中登錄是最常見且基礎(chǔ)的一種功能,本篇博客將詳細(xì)介紹如何使用 Spring Boot 和 Android 實現(xiàn)一個完整的登錄功能,需要的朋友可以參考下2024-11-11mybatis plus條件構(gòu)造器queryWrapper、updateWrapper
這篇文章主要介紹了mybatis plus條件構(gòu)造器queryWrapper、updateWrapper,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Spring?Boot?集成?Swagger2構(gòu)建?API文檔
這篇文章主要介紹了Spring?Boot?集成?Swagger2構(gòu)建?API文檔,通過使用?Swagger,我們只需要按照它所給定的一系列規(guī)范去定義接口以及接口的相關(guān)信息,然后它就能幫我們自動生成各種格式的接口文檔,方便前后端開發(fā)者進行前后端聯(lián)調(diào),下文需要的朋友可以參考一下2022-03-03Mybatis-Plus自動填充更新操作相關(guān)字段的實現(xiàn)
數(shù)據(jù)庫表中應(yīng)該都要有create_time、update_time字段;那么在開發(fā)中,對于這些共有字段的處理應(yīng)該要進行統(tǒng)一,這樣就可以簡化我們的開發(fā)過程。那么本文就對Mybatis-Plus中的字段自動填充進行記錄2021-11-11