Java8 ConcurrentHashMap 底层数据结构与核心方法流程
Java 8 ConcurrentHashMap 摒弃 Segment 锁,采用数组 + 链表 + 红黑树结构。并发控制结合 CAS 与 synchronized(桶级锁)。put 操作涉及初始化、定位、插入及扩容协助;get 操作无锁;size 基于 LongAdder 思想估算。核心优化包括锁粒度细化、协同扩容及读写分离,显著提升高并发性能。

Java 8 ConcurrentHashMap 摒弃 Segment 锁,采用数组 + 链表 + 红黑树结构。并发控制结合 CAS 与 synchronized(桶级锁)。put 操作涉及初始化、定位、插入及扩容协助;get 操作无锁;size 基于 LongAdder 思想估算。核心优化包括锁粒度细化、协同扩容及读写分离,显著提升高并发性能。

Java 8 的 ConcurrentHashMap 摒弃了 Java 7 中的 Segment 分段锁 机制,采用了与 HashMap 1.8 类似的 数组 + 链表 + 红黑树 的结构,但在并发控制上做了特殊设计。
ConcurrentHashMap ├── Node[] table (volatile) // 哈希桶数组
│ ├── Node (链表节点)
│ ├── TreeBin (红黑树节点包装)
│ └── ForwardingNode (扩容迁移标志)
├── Node[] nextTable // 扩容时的新数组
├── LongAdder baseCount // 基础计数
└── CounterCell[] counterCells // 并发计数单元格 (解决热点竞争)
key, val, hash, next。TreeBin 是红黑树的根节点包装,不直接存储数据,而是维护树的平衡。ForwardingNode,其 hash 值为 MOVED (-1),指向新的数组 nextTable,引导后续请求去新数组操作。table 数组和 Node 的 val、next 指针都是 volatile 的,保证可见性。putVal 是 put 的核心实现,流程较为复杂,主要步骤如下:
table 为 null 或长度为 0,调用 initTable()。sizeCtl < 0,说明其他线程正在初始化,当前线程让出 CPU (Thread.yield()) 自旋等待。sizeCtl 为扩容阈值(通常为容量的 0.75)。(n - 1) & hash 计算数组下标 i。f。这里分为三种情况:
ForwardingNode。helpTransfer(),当前线程会 协助扩容,将旧数组的数据迁移到新数组 nextTable 中。synchronized (f) 锁定该桶的头节点。onlyIfAbsent 决定是否覆盖 value。treeifyBin 尝试转为红黑树(需数组长度 >= 64,否则先扩容)。f 是 TreeBin 类型,调用 putTreeVal 插入树节点。addCount(1L, binCount) 更新元素个数。transfer() 进行扩容(容量翻倍)。public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// key 和 value 不能为空
if (key == null || value == null)
throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
// f = 目标位置元素
Node<K,V> f;
int n, i, fh; // fh 后面存放目标位置的元素 hash 值
if (tab == null || (n = tab.length) == 0)
// 数组桶为空,初始化数组桶(自旋+CAS)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
// no lock when adding to empty bin
} else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
;
(f) {
(tabAt(tab, i) == f) {
(fh >= ) {
binCount = ;
(Node<K,V> e = f;; ++binCount) {
K ek;
(e.hash == hash && ((ek = e.key) == key || (ek != && key.equals(ek)))) {
oldVal = e.val;
(!onlyIfAbsent)
e.val = value;
;
}
Node<K,V> pred = e;
((e = e.next) == ) {
pred.next = <K,V>(hash, key, value, );
;
}
}
} (f TreeBin) {
Node<K,V> p;
binCount = ;
((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != ) {
oldVal = p.val;
(!onlyIfAbsent)
p.val = value;
}
}
}
}
(binCount != ) {
(binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
(oldVal != )
oldVal;
;
}
}
}
addCount(, binCount);
;
}
get 操作是 无锁 的,这是 CHM 高性能的关键之一。
i = (n - 1) & hash。tab[i]。next 不为 null,遍历链表查找。TreeBin,调用 find 方法在红黑树中查找。ForwardingNode,说明正在扩容,需要在 nextTable (新数组) 中继续查找。volatile 保证读取到最新的数据。public V get(Object key) {
Node<K,V>[] tab;
Node<K,V> e, p;
int n, eh;
K ek;
// key 所在的 hash 位置
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
// 如果指定位置元素存在,头结点 hash 值相同
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
// key hash 值相等,key 值相同,直接返回元素 value
return e.val;
} else if (eh < 0) // 头结点 hash 值小于 0,说明正在扩容或者是红黑树,find 查找
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
// 是链表,遍历查找
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
Java 8 中 size() 的返回类型是 int,但底层统计逻辑基于 long。它不是 O(1) 的操作,而是一个估算值(在并发修改时)。
为了减少高并发下对单一计数变量的竞争,CHM 采用了类似 LongAdder 的机制:
CounterCell 单元格中,最后求和。baseCount。counterCells 数组,累加所有非空单元格的值。baseCount + counterCells 之和。size() 方法内部调用 sumCount(),如果结果超过 Integer.MAX_VALUE 则返回最大值,否则强转为 int。size() 返回的值可能不是强一致性的(即调用瞬间的真实大小),但在大多数场景下足够准确。如果需要强一致性大小,需要加锁统计,但性能会大幅下降。put 操作通过 addCount 方法更新计数,涉及 baseCount 和 CounterCell 数组的原子操作,具体实现细节可参考官方源码文档。
synchronized)。JVM 对 synchronized 做了大量优化(偏向锁、轻量级锁),在低冲突下性能极高。helpTransfer),加快了扩容速度,避免了单线程迁移导致的停顿。get 操作完全无锁,put 操作仅在冲突时加锁,极大提高了读多写少场景的性能。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online