ConcurrentHashMap源码阅读
ConcurrentHashMap是一个支持检索操作完全并发、更新操作高并发的哈希表。该类遵循与 java.util.Hashtable
相同的功能规范,并包含与
1 | {@code Hashtable} 完全互操作。 |
接下来看一看它和HashMap在内部实现上的一些区别。
spread
1 | /** |
前面运算这里不解释,跟hashMap一样,主要看后面的 & HASH_BITS
1 | 作用是消除负数。 |
这段代码的作用是原子性地读取哈希表(table)指定槽位(bin)上的节点,用于并发场景下安全地获取 ConcurrentHashMap 的桶元素。
tabAt 方法通过 Unsafe.getReferenceAcquire,以原子方式读取数组 tab 下标 i 处的元素,保证可见性和并发安全。
主要用于高并发下的哈希表读操作,比如 get、put、resize 等,避免数据竞争。
((long)i << ASHIFT) + ABASE 计算出数组元素的真实内存偏移量。
find
在 public V get(Object key) 方法中,eh < 0 表示当前桶的第一个节点(e)的 hash 字段是负数。这种情况有三种特殊节点:
- MOVED(-1):表示该桶是一个转发节点(ForwardingNode),说明哈希表正在扩容,当前桶的数据已经迁移到新的表,需要在新表中继续查找。
- TREEBIN(-2):表示该桶是树形结构的根节点(TreeBin),需要在红黑树中查找目标 key。
- RESERVED(-3):表示该节点是占位节点(ReservationNode),用于并发计算时的占位。
因此,eh < 0 时,get 方法会通过 e.find(h, key) 进入特殊的查找逻辑(如树查找或迁移查找),而不是普通链表查找。
put
put内部依然是putVal,这是高并发下安全插入/更新元素的主流程,兼顾性能和线程安全。
1 | /** Implementation for put and putIfAbsent */ |
主要作用和流程如下:
- 参数校验:key 或 value 为 null 时抛出异常。
- 计算 hash:对 key 的 hashCode 做扰动处理并去除负数,减少哈希冲突。
- 定位桶:通过 (n - 1) & hash 计算桶下标。
- 初始化表:如果 table 未初始化或长度为 0,则初始化。
- 无锁插入:如果目标桶为空,CAS 方式直接插入新节点,无需加锁。
- 扩容迁移:如果桶头节点 hash 为 MOVED,说明正在扩容,协助迁移。
- 只插入不存在的 key:如果 onlyIfAbsent 且 key 已存在,直接返回旧值。
- 加锁插入/更新:否则对桶头节点加锁,链表或树结构中查找/插入/更新节点。
- 链表:遍历查找,存在则更新,不存在则尾插新节点。
- 树:委托 TreeBin 处理。
- ReservationNode:递归更新异常。
- 链表转树:如果桶内节点数超过阈值,链表转红黑树。
- 计数:插入成功后更新元素计数。
- 返回:返回旧值或 null。