【面试题】ConcurrentHashMap实现线程安全的底层原理到底是什么?

本文详细解析了ConcurrentHashMap在JDK1.7与JDK1.8版本中实现线程安全的不同策略。JDK1.7采用分段锁机制,而JDK1.8通过CAS操作提高并发性能,并在必要时使用synchronized配合链表或红黑树处理哈希冲突。

JDK1.7以及之前的版本,多个数组,分段加锁,一个数组一个锁

JDK1.8及以后的版本,优化细粒度,整合为一个数组,对数组中每个元素进行CAS,如果CAS失败了说明当前有人了,此时synchronized对数组元素加锁,使用链表+红黑树进行处理,对数组每个元素加锁。

目前较多情况下,多线程要同时读写一个HashMap

原始用法

HashMap map = new HashMap();
synchronized(map){
    map.put("xxxx","yyyy");
}

如果线程1put数组[5],线程2put数组[21],其实这两个操作互不影响,除非同时对一个元素执行put操作,才需要多线程进行同步。

 

改用ConcurrentHashMap(JDK并发包中默认实现了线程安全性的ConcurrentHashMap)

ConcurrentHashMap map = new ConcurrentHashMap();
map.put("xxxx","yyyy");

在JDK1.7以及之前的版本里,分段[数组1],[数组2],[数组3],每个数组对应一个锁,分段加锁

JDK1.8之后进行了优化和改进,利用CAS策略,可以保证并发安全性。(只有对同一个元素进行操作才会用,而多线程对数组中不同的元素执行put时,其实互不影响)

同一时间,只有一个线程能成功执行这个CAS,就是说刚开始先获取一下数组[5]这个位置的值,如果为null,然后执行CAS,compareAndSet将值设置进去,同时其他的线程执行CAS都会失败

通过对数组每个元素执行CAS的策略,如果是很多线程对数组里不同的元素执行put,互不影响。如果其他线程失败了,表示数组[5]这个位置有线程完成了put操作。这个时候需要再这个位置基于链表+红黑树进行处理,synchronized(数组[5])加锁,基于链表或是红黑树在这个位置将自己的数据put进去

如果你是对数组里同一个位置的元素进行操作,才会加锁串行化处理;如果是对数组不同位置的元素进行操作,此时大家可以并发执行

 

【评论区】

看一下put操作的流程(初始化,上锁,扩容,树化,synchronized)过程

从源码上看,PUT操作只有在NULL时才会CAS

1.ConcurrentHashMap进行put操作的时候,元素为null,则进行cas,不为null,则进行synchronized同步,不为null的时候,为什么也不进行cas呢?(这块的解答看不懂)

因为此时数组得位置放的是链表或者红黑树得引用.而不是具体得值

当数组得位置存放得是链表或者红黑树得引用得时候,此时已经发生了一次哈希冲突了,讲道理,哈希冲突得概率有,但不是很高,链表里面得数据进行添加得时候,运用cas操作是比较麻烦得,因为在多线程情况下往链表里面插数据是会报错得,这个时候只能采用sync锁来进行了.cas是比较后set,那对链表来讲,这个操作是实现不了的,链表在内存中不连续,节点的增加记录下上一个的位置就行,cas操作跟谁比较,怎么比较,hashmap的entry是个单向链表,cas操作中链表的指针如果不为null,那就得到另一个地址在进行比较,在这个过程中,链表长度突破8,对于后续的转化红黑树的操作影响也是非常巨大的.总的来讲,就是这个过程中进行cas的消耗,以及对编码的复杂度,都没有sync来的方便快捷,哈希冲突也是个小概率事件,对数组的不同位置加锁或者cas操作,已经完全够用了

 

元素不为null,当然不能走CAS,因为数组的元素实际上是一个链表或者红黑树的节点的引用,你要怎么对该节点直接CAS呢?只能先用synchronized(数组元素)锁住后,再对链表或者红黑树做增删操作

 

### Java 中 ConcurrentHashMap 实现线程安全底层原理 #### 什么是 ConcurrentHashMap? `ConcurrentHashMap` 是一种支持高并发场景下的线程安全 Map 结构,它允许多个线程同时对其进行读写操作而不会发生数据不一致的问题[^2]。相比传统的 `Hashtable` 和带有 `synchronized` 锁的 `HashMap`,`ConcurrentHashMap` 提供了更高的吞吐量和更低的竞争开销。 --- #### ConcurrentHashMap 的核心设计思想 为了提高性能,`ConcurrentHashMap` 并没有像 `Hashtable` 或者 `Collections.synchronizedMap()` 那样对整个表加锁,而是采用了更细粒度的锁分离策略——**分段锁(Segment Lock)**。具体来说: - 在 JDK 7 中,`ConcurrentHashMap` 将内部的数据分为若干个 Segment 对象,每个 Segment 类似于一个小的 HashTable,并且有自己的锁。这样可以使得不同的线程可以在不同的 Segment 上进行操作而不互相干扰。 - 在 JDK 8 中,`ConcurrentHashMap` 改进了其底层结构,去掉了 Segment 的概念,转而使用了一种更加高效的实现方式:**数组 + 链表 + 红黑树**[^1]。 --- #### JDK 8 中 ConcurrentHashMap底层实现细节 ##### 数据结构概述 在 JDK 8 中,`ConcurrentHashMap` 的底层由以下几个部分组成: 1. **Node 数组**:类似于普通的 HashMap,`ConcurrentHashMap` 使用了一个 Node 数组作为主要存储容器。 2. **链表**:当哈希冲突时,节点会被链接成一条单向链表。 3. **红黑树**:如果链表长度超过一定阈值(默认为 8),则会将链表转换为红黑树以减少查找的时间复杂度[^1]。 ##### 关键特性 1. **CAS 操作**:`ConcurrentHashMap` 大量依赖于无锁算法中的 CAS(Compare And Swap)技术来进行高效的状态更新。例如,在插入新元素时,先尝试通过 CAS 更新指定位置上的引用;只有当 CAS 失败时才会考虑升级到更高层次的同步手段。 2. **分段锁机制**:虽然 JDK 8 移除了显式的 Segments,但实际上仍然保留了类似的思路。对于每一次修改操作(如 put、remove),只会针对涉及的具体桶位施加重入锁(ReentrantLock),而不是全局锁定整个表[^2]。 3. **帮助式传播(Helpful Propagation)**:为了避免长时间持有锁带来的性能瓶颈,`ConcurrentHashMap` 设计了一些辅助函数让其他线程也能参与到一些耗时任务当中,比如扩容过程中的迁移工作就可以被任何发现正在进行 resize 的线程主动接手完成一部分[^1]。 --- #### 如何保证线程安全性? 1. **读操作无需加锁** - 由于引入了 JMM (Java Memory Model)的支持,`ConcurrentHashMap` 能够确保即使是在多线程环境下,简单的 get 请求也可以安全地返回最新版本的结果,而不需要额外付出同步成本[^3]。 2. **写操作局部化保护** - 插入或删除等变更型 API 主要集中在特定索引处执行,因此只需对该区域实施短暂封锁即可满足需求。例如,在调用 `putVal(K,V)` 方法期间,仅需占有对应槽位所属的 ReentrantLock 实例便可顺利推进后续逻辑[^2]。 3. **动态调整容量大小** - 当现有空间不足以容纳新增项时,`ConcurrentHashMap` 会自动触发扩展流程。值得注意的是,这一阶段同样遵循最小影响原则,即尽量不影响正常运转的服务请求。此外,得益于前面提到的帮助式传播理念,即便原作者暂时离开现场,后来者依旧有机会接力承担起剩余职责直至彻底结束整个重构周期。 --- #### 示例代码分析 以下是一个简化版的 `putIfAbsent` 方法演示如何利用 CAS 来保障原子性和一致性: ```java final V putVal(int hash, K key, V value) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) != null && (n = tab.length) > 0) { // 计算定位索引 if ((p = tab[i = (n - 1) & hash]) == null) casTabAt(tab, i, null, new Node<K,V>(hash, key, value)); // 使用 CAS 创建首节点 else { lockForPut(p); // 加锁后再继续深入判断 try { Object k; if (valueMatch(p.value)) // 已存在相同key,则跳过赋值 return p.value; else // 新增情况走常规路径 addBinTreeOrList(hash, key, value); } finally { unlock(); // 不管怎样都要记得解锁哦~ } } } return null; } ``` --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值