ConcurrentHashMap小结

本文详细解析了ConcurrentHashMap的内部结构与实现机制,探讨其如何在多线程环境下高效进行并发操作,为读者提供了深入理解Java并发容器的基础。

ConcurrentHashMap底层是怎样的结构?

1、ConcurrentHashMap类的结构

163710_D7d6_2423525.png

 

 

转载于:https://my.oschina.net/u/2423525/blog/750820

<think> 我们正在讨论ConcurrentHashMap的底层原理及使用案例。 根据引用[2]: ConcurrentHashMap通过分段锁(JDK 1.7)和CAS + synchronized(JDK 1.8)等机制来实现高效的并发访问。 引用[3]提到了JDK1.7的实现,引用[5]提到JDK1.8中通过Node节点加锁(即CAS+synchronized)来实现。 因此,我们需要分JDK1.7和JDK1.8两个版本来讲解底层原理。同时,引用[4]指出,初始化是在put操作中实现的,这也是一个特点。 此外,用户要求使用案例,我们可以给出一些典型的使用场景和代码片段。 注意:按照要求,我们需要在回答中自然地添加引用标识,并在最后生成相关问题。 结构安排: 1. JDK1.7的ConcurrentHashMap原理(分段锁) 2. JDK1.8的ConcurrentHashMap原理(CAS + synchronized) 3. 使用案例(代码示例) 4. 小结 5. 相关问题 </think> ### ConcurrentHashMap 底层原理详解 ConcurrentHashMap 通过分段锁(JDK 1.7)和 `CAS + synchronized`(JDK 1.8)实现线程安全的高效并发访问,以下是核心原理: --- #### **1. JDK 1.7 分段锁机制** **设计思想**: - 将整个哈希表划分为多个 **Segment(段)**,每个段独立加锁 - 默认 16 个段(并发级别),不同线程可同时操作不同段 - 段继承自 `ReentrantLock`,实现细粒度锁控制 **存储结构**: $$ \begin{array}{c} \text{ConcurrentHashMap} \\ \downarrow \\ \boxed{\text{Segment[0]}} \quad \boxed{\text{Segment[1]}} \quad \cdots \quad \boxed{\text{Segment[15]}} \\ \downarrow \\ \text{HashEntry[]} \quad \text{(每个段内是独立哈希表)} \end{array} $$ **操作流程**(以 `put()` 为例): 1. 根据 key 的哈希值确定目标 Segment 2. 获取该 Segment 的锁(其他段不受影响) 3. 在段内哈希表中执行插入操作[^2][^3] --- #### **2. JDK 1.8 优化:CAS + synchronized** **核心改进**: - 废弃分段锁,采用 **`Node数组 + 链表/红黑树`** 结构(同 HashMap) - 锁粒度细化到 **单个桶(桶头节点)**,通过 `synchronized` 锁定 - 使用 **CAS 操作**保证原子性更新(如 `sizeCtl` 控制扩容) **关键实现**: ```java final V putVal(K key, V value) { if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); Node<K,V>[] tab; Node<K,V> f; // CAS 初始化表(延迟加载) if (tab = table) == null) tab = initTable(); // CAS 定位桶头节点 else if ((f = tabAt(tab, i)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; } else { synchronized (f) { // 锁定桶头节点 // 插入链表或红黑树 } } addCount(1L, binCount); // CAS 更新元素计数 } ``` **优化点**: - 扩容时支持多线程协同搬运数据(`transfer()` 方法) - 当链表长度 > 8 时转换为红黑树(避免哈希冲突性能退化)[^5] --- ### 使用案例 #### **场景 1:高并发计数器** ```java ConcurrentHashMap<String, AtomicInteger> counterMap = new ConcurrentHashMap<>(); void increment(String key) { counterMap.computeIfAbsent(key, k -> new AtomicInteger(0)) .incrementAndGet(); // 线程安全计数 } ``` **说明**:利用 `AtomicInteger` 和 `computeIfAbsent` 避免显式同步[^5] #### **场景 2:缓存系统** ```java ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>(); Object get(String key) { return cache.computeIfAbsent(key, k -> { // 缓存未命中时加载数据(线程安全) return loadDataFromDB(k); }); } ``` **优势**:避免重复加载数据和缓存击穿问题 #### **场景 3:会话管理** ```java ConcurrentHashMap<String, UserSession> sessionMap = new ConcurrentHashMap<>(); void updateSession(String sessionId, UserSession session) { sessionMap.put(sessionId, session); // 线程安全更新 } ``` --- ### 性能对比 | 操作 | JDK 1.7 (分段锁) | JDK 1.8 (CAS+syn) | |--------------|------------------------|-------------------------| | **读操作** | 无需锁(volatile读) | 无锁(volatile读) | | **写冲突** | 段内串行 | 桶内串行 | | **扩容** | 段独立扩容 | 多线程协同扩容 | | **锁粒度** | 段级(默认16个) | 桶级(可多达数万个) | --- ### 小结 1. **JDK 1.7**:分段锁降低锁竞争,但扩容和查询效率受限 2. **JDK 1.8**: - 锁粒度细化到桶,结合 CAS 提升并发度 - 红黑树优化哈希冲突性能 - 多线程协同扩容减少停顿[^4][^5] 3. **使用原则**: - 优先用于高并发读写场景 - 避免使用 `size()`(遍历所有段/桶,开销大) - 批量操作用 `forEach()` 替代迭代器 --- ### 相关问题 1. ConcurrentHashMap 在 JDK 1.8 中如何避免死锁? 2. 为什么 ConcurrentHashMap 不允许 null 键/值? 3. ConcurrentHashMap 和 Collections.synchronizedMap 有何性能差异?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值