ConcurrentHashMap学习记录
ConcurrentHashMap 概述
ConcurrentHashMap 是 Java 并发包(java.util.concurrent)中最重要的并发容器之一,专为多线程环境设计,用于解决哈希表的线程安全问题,同时保持高效的并发性能。它的核心目标是在保证线程安全的前提下,尽可能减少锁竞争,支持高并发的读写操作。
为什么需要 ConcurrentHashMap
在 ConcurrentHashMap 出现之前,处理哈希表的线程安全有两种方案:
- Hashtable:通过 synchronized 修饰所有方法,对所有共享资源整体上锁,当一个线程访问该资源时,其它线程必须阻塞等待,并发性能差
- 内部通过包装 HashMap,使用一个全局锁(mutex)保证安全,同样是全表锁,
ConcurrentHashMap 的出现打破了全表锁,大大提升了并发效率
JDK 1.7 中的实现方式
核心采用 “分段锁(Segment)” 机制,这是一种将哈希表分割为多个独立段(Segment),每段单独加锁的设计
每个 Segment 数组就是一个小的哈希表,自身继承了 ReentrantLock,即每一个 Segment 就是一个哈希表,Segment 中的每一个元素可以当作一个链表头节点,当有重复元素映射到此链表中,那么就会存储到这个链表。
核心操作原理:
- 定位 Segment:根据 key 的哈希值计算出对应的 Segment 索引,确定操作的 Segment 段
- 获取 Segment 锁:调用 Segment 的 lock()方法上锁
- 在当前 Segment 对应的子数组中执行插入逻辑
- 调用 unlock()方法释放锁资源
不同 Segment 上的操作完全并行。例如,线程 A 操作 Segment[0],线程 B 操作 Segment[1],两者无需竞争锁,可同时执行。
1 | 读操作无需加锁,子链表的 value 被 volatile 修饰,一个线程修改后,其他线程能立即看到最新值 |
JDK 1.8 后的 ConcurrentHashMap
废弃了分段锁,采用了 CAS 无锁算法和 synchronized 来进行了重构,进一步提升了并发性能。
底层结构:数组(Node)+ 链表 + 红黑树
- Node 数组:维护了一个哈希桶,存储键值对
- 链表与红黑树:当哈希冲突导致链表长度超过阈值(默认 8),且数组容量 ≥ 64 时,链表会转为红黑树(提升查询效率,从 O (n) 到 O (logn))
核心操作原理
- 计算哈希与索引:根据 key 的哈希值计算数组索引
- 初始化数组:通过 CAS 原子操作初始化数组
- 无哈希冲突:直接通过 CAS 插入新节点
- 有哈希冲突:
- 若桶内时链表,对链表的头节点加 synchronized 锁,遍历链表查找或插入节点;若插入后链表长度超过阈值,转为红黑树。
- 若桶内是红黑树:对红黑树的根节点加 synchronized 锁,执行插入操作。
- 扩容检查:若元素数量超过阈值(负载因子 * 容量),触发并发扩容(多线程协同迁移数据)。
核心特性
- 线程安全:通过锁机制(分段锁或节点锁)+ CAS 保证多线程读写安全,避免了 HashMap 并发修改时的死循环、数据丢失等问题。
- 高效并发:细粒度锁设计减少锁竞争,读操作无锁,写操作仅锁定冲突节点,支持高并发场景
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Myskill-blog!