ConcurrentHashMap 如何保证线程安全?
-
JDK7:分段锁机制,不同
Segment可并行写入 -
JDK8:桶级别锁(头节点加
synchronized)+ CAS(如putVal())
主要就是为了应对hashmap在并发环境下不安全而诞生的,ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响。
我们都知道Map一般都是数组+链表结构(JDK1.8该为数组+红黑树)。
ConcurrentHashMap避免了对全局加锁改成了局部加锁操作,这样就极大地提高了并发环境下的操作速度,由于ConcurrentHashMap在JDK1.7和1.8中的实现非常不同,接下来我们谈谈JDK在1.7和1.8中的区别。
ConcurrentHashMap 是 Java 中用于实现线程安全的哈希表(Map)实现类,位于 java.util.concurrent 包中。它在保证高并发性能的同时,提供了线程安全的操作机制,是多线程环境下推荐使用的 Map 实现。
一、ConcurrentHashMap 的核心特性
| 特性 | 描述 |
|---|---|
| 线程安全 | 多个线程可以同时读写而不会导致数据不一致或抛出异常。 |
| 高性能 | 相比 Collections.synchronizedMap(new HashMap()) 或 Hashtable,性能更高,因为它采用了分段锁机制。 |
| 弱一致性迭代器 | 迭代器不会抛出 ConcurrentModificationException,但可能不会立即反映最新的修改。 |
二、实现原理(以 JDK 1.8 为例)
1. 结构设计:数组 + 链表 + 红黑树
ConcurrentHashMap使用类似于HashMap的结构:数组 + 链表/红黑树。- 每个桶(bucket)对应一个链表或红黑树,当链表长度超过阈值(默认为 8)时转换为红黑树,提升查找效率。
2. CAS + synchronized 控制并发
- 不再使用
Segment分段锁(JDK 1.7 及之前),而是采用更细粒度的锁机制:
-
- CAS(Compare and Swap) :用于无锁更新操作,如
putIfAbsent、replace等。 - synchronized:对某个桶(链表或红黑树的根节点)加锁,只锁定当前正在操作的节点,而不是整个 Map。
- CAS(Compare and Swap) :用于无锁更新操作,如
3. 扩容机制优化
- 在扩容时,支持多线程并发迁移(transfer),每个线程负责一部分桶的迁移工作。
- 扩容过程中允许继续读写,提升了并发性能。
三、线程安全的方法
| 方法 | 是否线程安全 | 说明 |
|---|---|---|
| get(Object key) | ✅ | 无锁,使用 volatile 保证可见性 |
| put(K key, V value) | ✅ | CAS + synchronized 实现线程安全 |
| remove(Object key) | ✅ | 同上 |
putIfAbsent(K key, V value) | ✅ | 如果不存在才插入,利用 CAS |
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) | ✅ | 如果存在则计算并更新 |
forEach(BiConsumer<? super K, ? super V> action) | ✅ | 支持并发遍历 |
| keySet() / values() / entrySet() | ⚠️ 弱一致性 | 迭代过程不阻塞,但可能看到旧数据 |
四、与 Hashtable 和 SynchronizedMap 的区别
| 对比项 | ConcurrentHashMap | Hashtable | Collections.synchronizedMap(new HashMap()) |
|---|---|---|---|
| 锁粒度 | 桶级别(更细) | 整个 Map(粗) | 整个 Map(粗) |
| 性能 | 高 | 低 | 低 |
| 允许 null 值 | key/value 都不允许为 null | key/value 都不允许为 null | key/value 都不允许为 null |
| 迭代器 | 弱一致性 | fail-fast | fail-fast |
| 扩容策略 | 并发扩容 | 单线程扩容 | 单线程扩容 |
五、应用场景
| 场景 | 推荐使用 ConcurrentHashMap 的原因 |
|---|---|
| 缓存系统 | 高并发下频繁读写缓存键值对 |
| 统计计数器 | 多线程统计访问次数等信息 |
| 注册中心 | 存储服务实例和服务名之间的映射关系 |
| 状态管理 | 多线程共享状态变量,如任务执行状态 |
六、注意事项
- 不能保证复合操作的原子性
如if-put或put-if-absent,需要使用putIfAbsent、computeIfPresent等方法。 - 迭代器是弱一致性的
在迭代期间其他线程修改了内容,迭代器可能不会立即感知到变化。 - 不支持 null 键和 null 值
为了避免歧义(null 表示不存在还是存在但值为 null)。 - 不要依赖其顺序性
ConcurrentHashMap不保证元素的顺序。
七、总结
ConcurrentHashMap 是 Java 并发编程中最常用的线程安全 Map 实现,具有以下优势:
✅ 高并发性能
✅ 线程安全
✅ 支持并发扩容
✅ 提供丰富的线程安全操作方法
合理使用 ConcurrentHashMap 可以显著提高多线程程序的性能和稳定性,适用于大多数并发场景下的键值存储需求。