线程安全集合——ConcurrentHashMap
文章目录
- 前言
- 一、ConcurrentHashMap是什么?
- 二、为什么需要ConcurrentHashMap?
- 2.1 HashMap的问题
- 2.2 Hashtable的局限性
- 2.3 ConcurrentHashMap的优势
- 三、如何使用ConcurrentHashMap
- 3.1 基本用法
- 3.2 常用方法
- 3.3 多线程环境下的使用
- 四、底层实现原理
- 4.1 JDK 1.8之前:分段锁机制
- 4.2 JDK 1.8及以后:CAS + synchronized
- 4.3 核心方法实现概述
- put() 方法
- get() 方法
前言
在多线程开发中,HashMap虽然性能很好,但它不是线程安全的,在并发环境下可能会出现数据不一致甚至死循环的问题。虽然我们可以使用Hashtable或Collections.synchronizedMap()来获得线程安全的Map,但它们的性能在高并发场景下并不理想。这时候,ConcurrentHashMap就派上用场了。
一、ConcurrentHashMap是什么?
ConcurrentHashMap 是Java提供的一个线程安全的哈希表实现,它是Map接口的实现类。与Hashtable不同,ConcurrentHashMap采用了更加精细的锁机制,在保证线程安全的同时,大大提高了并发性能。
主要特点:
- 线程安全,支持高并发读写
- 性能优于Hashtable和Collections.synchronizedMap()
- 不允许null键和null值
- 迭代器具有弱一致性
二、为什么需要ConcurrentHashMap?
2.1 HashMap的问题
// HashMap在多线程环境下的问题演示
Map<String, Integer> map = new HashMap<>();// 多个线程同时操作可能导致数据不一致
new Thread(() -> {for (int i = 0; i < 1000; i++) {map.put("key" + i, i);}
}).start();new Thread(() -> {for (int i = 0; i < 1000; i++) {map.put("key" + i, i);}
}).start();
2.2 Hashtable的局限性
// Hashtable虽然线程安全,但性能较差
Hashtable<String, Integer> hashtable = new Hashtable<>();
// 每个方法都使用synchronized,并发性能不佳
2.3 ConcurrentHashMap的优势
// ConcurrentHashMap既保证线程安全,又有更好的性能
ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 采用分段锁机制(JDK 1.8后改为CAS+synchronized)
三、如何使用ConcurrentHashMap
3.1 基本用法
public class ConcurrentHashMapExample {public static void main(String[] args) {// 创建ConcurrentHashMapConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 基本操作map.put("apple", 10);map.put("banana", 20);map.put("orange", 30);// 线程安全的获取操作Integer value = map.get("apple");System.out.println("apple的数量: " + value);// 线程安全的删除操作map.remove("banana");// 遍历操作map.forEach((key, val) -> {System.out.println(key + " -> " + val);});System.out.println("map大小: " + map.size());}
}
3.2 常用方法
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 1. 基本操作
map.put("key1", 100);
map.putIfAbsent("key2", 200); // 如果key不存在才放入
Integer value = map.get("key1");
map.remove("key1");// 2. 原子操作方法
map.replace("key2", 200, 300); // 原子性替换
map.compute("key3", (k, v) -> v == null ? 1 : v + 1); // 原子性计算// 3. 批量操作
map.putAll(Map.of("key4", 400, "key5", 500));
3.3 多线程环境下的使用
public class ConcurrentTest {private static ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();public static void main(String[] args) throws InterruptedException {// 创建多个线程同时操作Thread[] threads = new Thread[10];for (int i = 0; i < 10; i++) {final int threadId = i;threads[i] = new Thread(() -> {for (int j = 0; j < 1000; j++) {String key = "thread" + threadId + "_key" + j;map.put(key, j);}});threads[i].start();}// 等待所有线程完成for (Thread thread : threads) {thread.join();}System.out.println("最终map大小: " + map.size());}
}
四、底层实现原理
4.1 JDK 1.8之前:分段锁机制
在JDK 1.8之前,ConcurrentHashMap采用分段锁(Segment)机制:
- 将整个哈希表分成多个段(Segment)
- 每个段都有自己的锁
- 不同段之间的操作可以并发进行
4.2 JDK 1.8及以后:CAS + synchronized
JDK 1.8对ConcurrentHashMap进行了重大改进:
- 取消了分段锁机制
- 采用CAS(Compare-And-Swap)+ synchronized的方式
- 数据结构改为数组 + 链表 + 红黑树
4.3 核心方法实现概述
put() 方法
// 简化版的put方法逻辑
public V put(K key, V value) {// 1. 计算hash值int hash = hash(key);// 2. 根据hash值找到对应的桶Node<K,V>[] tab = table;int i = (tab.length - 1) & hash;// 3. 如果桶为空,使用CAS操作插入if (tab[i] == null) {// CAS操作,无锁插入casTabAt(tab, i, new Node<>(hash, key, value, null));} else {// 4. 如果桶不为空,使用synchronized锁住头节点synchronized (tab[i]) {// 在链表或红黑树中查找并插入}}
}
实现概述: 通过CAS操作处理空桶的插入,对于非空桶则使用synchronized锁住头节点,这样可以最大化并发性能。
get() 方法
// 简化版的get方法逻辑
public V get(Object key) {// 1. 计算hash值int hash = hash(key);// 2. 根据hash值找到对应的桶Node<K,V>[] tab = table;int i = (tab.length - 1) & hash;// 3. 无锁读取Node<K,V> e = tab[i];while (e != null) {if (e.hash == hash && key.equals(e.key)) {return e.val;}e = e.next;}return null;
}
实现概述: 读操作完全无锁,通过volatile保证数据的可见性,极大提高了读取性能。