1. JUC并發容器概述
Java集合容器框架主要有四大類別:List、Set、Queue、Map。常見的ArrayList、LinkedList、HashMap等容器都是非線程安全的。
Java提供了同步容器(如Vector、Hashtable、SynchronizedList)通過synchronized實現同步,但會削弱并發性,降低吞吐量。為解決性能問題,java.util.concurrent包提供了多種并發類容器。
2. CopyOnWriteArrayList
2.1 概述
- 對應的非并發容器:ArrayList
- 目標:代替Vector、synchronizedList
- 原理:利用讀多寫少的特性,讀操作不加鎖,寫操作時先復制新集合,修改后替換舊引用,通過volatile保證可見性
2.2 應用場景
- 讀多寫少的場景:讀取頻率遠高于寫入頻率的緩存
- 不需要實時更新的數據:如日志緩沖批量寫入
2.3 基本使用
// 創建CopyOnWriteArrayList對象
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();// 添加元素
list.add("element1");
list.add("element2");// 設置元素(指定下標)
list.set(0, "newElement");// 獲取元素
String element = list.get(0);// 刪除元素
list.remove(0);
list.remove("element2");// 其他操作
boolean isEmpty = list.isEmpty();
boolean contains = list.contains("element1");
int size = list.size();
list.clear();
2.4 IP黑名單判定示例
public class CopyOnWriteArrayListDemo {private static CopyOnWriteArrayList<String> blacklist = new CopyOnWriteArrayList<>();// 模擬初始黑名單數據static {blacklist.add("192.168.1.1");blacklist.add("192.168.1.2");blacklist.add("192.168.1.3");}public static void main(String[] args) {// 模擬請求處理Runnable requestHandler = () -> {try {Thread.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}String clientIP = "192.168.1." + new Random().nextInt(6);if (blacklist.contains(clientIP)) {System.out.println(Thread.currentThread().getName() + " IP " + clientIP + " 命中黑名單,拒絕訪問");return;}System.out.println(Thread.currentThread().getName() + " IP " + clientIP + " 允許訪問");};// 啟動多個請求線程for (int i = 1; i <= 5; i++) {new Thread(requestHandler, "請求-" + i).start();}// 黑名單更新線程new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}String newBlackIP = "192.168.1.4";blacklist.add(newBlackIP);System.out.println("系統更新: 添加新黑名單IP " + newBlackIP);}, "黑名單更新").start();}
}
2.5 實現原理
采用"寫時復制"機制:
- 寫操作時創建新數組,復制原始數組內容
- 在新數組上進行修改操作
- 將引用指向新數組,通過volatile保證可見性
- 讀操作直接訪問數組,無需加鎖
2.6 缺陷
- 內存消耗:寫操作需要拷貝數組,可能引發GC
- 數據一致性:不能保證實時一致性,只能保證最終一致性
- 性能問題:數據量大時寫操作代價高昂
2.7 Fail-Fast vs Fail-Safe機制
Fail-Fast機制
- 特點:快速失敗,檢測到并發修改立即拋出ConcurrentModificationException
- 實現:java.util包中的集合類(ArrayList、HashMap等)
- 解決方案:
- 使用synchronized(不推薦,影響性能)
- 使用CopyOnWriteArrayList(推薦)
Fail-Safe機制
- 特點:安全失敗,在復制的集合上修改,不拋出異常
- 實現:java.util.concurrent包中的集合類
- 缺點:
- 數據非實時一致
- 內存占用更高(需要復制)
- 可能引起頻繁GC
3. ConcurrentHashMap
3.1 概述
- 對應的非并發容器:HashMap
- 目標:代替Hashtable、synchronizedMap,支持復合操作
- 原理:
- JDK6:分段鎖機制
- JDK8:CAS + synchronized
3.2 應用場景
- 共享數據的線程安全:多線程環境下的數據讀寫
- 緩存實現:高并發緩存數據結構
3.3 基本使用
// 創建ConcurrentHashMap對象
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 添加鍵值對
map.put("key1", 1);
map.put("key2", 2);// 批量添加
HashMap<String, Integer> tempMap = new HashMap<>();
tempMap.put("key3", 3);
tempMap.put("key4", 4);
map.putAll(tempMap);// 獲取值
Integer value = map.get("key1");// 特殊方法
map.putIfAbsent("key1", 100); // 不存在則put,返回null;存在返回當前值
map.remove("key1", 1); // 鍵值匹配才刪除
map.replace("key2", 2, 20); // 鍵值匹配才替換// 其他操作
boolean isEmpty = map.isEmpty();
int size = map.size();
Set<String> keys = map.keySet();
Collection<Integer> values = map.values();
map.clear();
3.4 單詞統計示例
public class ConcurrentHashMapDemo {private static ConcurrentHashMap<String, AtomicLong> wordCountMap = new ConcurrentHashMap<>();private static CountDownLatch latch = new CountDownLatch(3);private static String[] words = {"apple", "banana", "orange", "apple", "banana"};public static void main(String[] args) throws InterruptedException {Runnable counterTask = () -> {for (int i = 0; i < 5; i++) {String word = words[new Random().nextInt(words.length)];// 獲取當前計數,不存在則初始化AtomicLong count = wordCountMap.get(word);if (count == null) {AtomicLong newCount = new AtomicLong(0);count = wordCountMap.putIfAbsent(word, newCount);if (count == null) {count = newCount;}}// 增加計數count.incrementAndGet();System.out.println(Thread.currentThread().getName() + ": " + word + " 計數: " + count.get());}latch.countDown();};// 啟動多個計數線程for (int i = 1; i <= 3; i++) {new Thread(counterTask, "計數器-" + i).start();}latch.await();System.out.println("最終統計結果: " + wordCountMap);}
}
3.5 數據結構演進
HashTable結構
- 全表鎖,性能低下
JDK1.7 ConcurrentHashMap
- 結構:Segment數組 + HashEntry數組 + 鏈表
- 機制:分段鎖,寫操作分散到不同段
JDK1.8+ ConcurrentHashMap
- 結構:數組 + 鏈表 + 紅黑樹(同HashMap)
- 機制:CAS + synchronized
- 樹化條件:
- 鏈表節點數 ≥ 8(TREEIFY_THRESHOLD)
- 數組長度 ≥ 64(MIN_TREEIFY_CAPACITY)
4. ConcurrentSkipListMap
4.1 概述
- 對應的非并發容器:TreeMap
- 特點:基于跳表實現的線程安全有序Map
- 優勢:支持高并發有序訪問和區間查詢
4.2 跳表(Skip List)原理
跳表是基于有序鏈表的概率型數據結構,支持O(log n)時間復雜度的查找、插入、刪除操作。
跳表特性
- 多層鏈表結構組成
- 每層都是有序鏈表
- 最底層包含所有元素
- 高層元素必定在低層出現
- 節點包含兩個指針:同級下一個元素、下層相同值元素
跳表操作
- 查找:從最高層開始,向右查找直到大于目標值,然后向下一層繼續
- 插入:
- 隨機確定插入層級K
- K大于當前層級時創建新層
- 申請新節點并調整指針
4.3 基本使用
public class ConcurrentSkipListMapDemo {public static void main(String[] args) {ConcurrentSkipListMap<Integer, String> skipListMap = new ConcurrentSkipListMap<>();// 添加元素(自動排序)skipListMap.put(3, "Value3");skipListMap.put(1, "Value1");skipListMap.put(4, "Value4");skipListMap.put(2, "Value2");// 獲取元素String value = skipListMap.get(2);System.out.println("Key=2的值: " + value);// 遍歷元素(有序)System.out.println("按順序遍歷:");for (Integer key : skipListMap.keySet()) {System.out.println(key + " : " + skipListMap.get(key));}// 范圍查詢System.out.println("Key在1-3之間的元素:");ConcurrentNavigableMap<Integer, String> subMap = skipListMap.subMap(1, true, 3, true);subMap.forEach((k, v) -> System.out.println(k + " : " + v));// 刪除元素String removedValue = skipListMap.remove(3);System.out.println("刪除的值: " + removedValue);}
}
5. 其他并發容器(部分不常用)
5.1 CopyOnWriteArraySet
- 對應的非并發容器:HashSet
- 原理:基于CopyOnWriteArrayList實現
- 特點:使用addIfAbsent方法保證元素唯一性
5.2 并發Queue
- ArrayBlockingQueue:數組實現的有界阻塞隊列
- LinkedBlockingQueue:鏈表實現的可選有界隊列
- ConcurrentLinkedQueue:高性能非阻塞隊列
- PriorityBlockingQueue:支持優先級的無界阻塞隊列
5.3 并發Deque
- ConcurrentLinkedDeque:并發雙端隊列
- LinkedBlockingDeque:鏈表實現的雙端阻塞隊列
6. 性能考量與最佳實踐
6.1 性能影響因素
- 并發級別:根據實際并發訪問量選擇合適容器
- 讀寫比例:讀多寫少選CopyOnWrite,寫多選ConcurrentHashMap
- 數據量大小:大數據量考慮ConcurrentSkipListMap
- 一致性要求:強一致選Hashtable,弱一致選并發容器
6.2 最佳實踐
- 明確需求:根據業務場景選擇最合適的容器
- 性能測試:在實際負載下測試容器性能
- 監控GC:關注并發容器可能引起的內存和GC問題
- 避免過度設計:簡單場景使用簡單解決方案
// 容器選型決策示例
public class ContainerSelector {public static <K, V> Map<K, V> createMap(boolean needOrdering, int expectedSize, int concurrencyLevel) {if (needOrdering) {return new ConcurrentSkipListMap<>();} else if (expectedSize > 1000000 || concurrencyLevel > 100) {return new ConcurrentHashMap<>(expectedSize, 0.75f, concurrencyLevel);} else {return new ConcurrentHashMap<>();}}public static <E> List<E> createList(boolean readHeavy, int expectedSize) {if (readHeavy && expectedSize < 10000) {return new CopyOnWriteArrayList<>();} else {return Collections.synchronizedList(new ArrayList<>());}}
}
選型總結
場景特點 | 推薦容器 | 理由 |
---|---|---|
鍵值對操作,高并發 | ConcurrentHashMap | 線程安全,性能優良 |
大數據量有序訪問 | ConcurrentSkipListMap | 跳表結構,高效增刪 |
讀多寫少,數據量小 | CopyOnWriteArrayList | 讀無鎖,寫時復制 |
強一致性要求 | Hashtable | 全表鎖,保證強一致 |