ConcurrentHashMap是 Java 并发编程中线程安全、高并发的哈希表实现核心解决了HashMap线程不安全、HashTable效率低的问题。本文基于JDK 1.8源码讲解1.7 采用分段锁1.8 升级为CAS synchronized红黑树结构性能大幅提升全程结合核心源码把put存值、get取值的每一步讲透。前置核心概念必须先看懂数据结构数组 链表 红黑树链表长度 ≥ 8 且数组长度 ≥ 64 时链表转为红黑树树节点 ≤ 6 时退化为链表线程安全核心无锁CAS操作无锁更新节点有锁synchronized 锁头节点只锁链表 / 树的头节点不锁整个数组并发度极高关键常量 / 变量table哈希表数组懒加载第一次 put 才初始化sizeCtl控制数组初始化 / 扩容-1 表示正在初始化-N 表示正在扩容hashkey 的哈希值高 16 位参与运算减少哈希冲突next链表 / 树的下一个节点一、put () 方法存值全流程源码 逐行解析1. put () 入口方法public V put(K key, V value) { // 调用核心 putVal 方法onlyIfAbsentfalsekey 重复则覆盖 return putVal(key, value, false); }2. 核心 putVal () 源码逐行注释这是ConcurrentHashMap最核心的方法所有存值逻辑都在这里final V putVal(K key, V value, boolean onlyIfAbsent) { // 1. 校验key 和 value 都不允许为 null和 HashMap 不同 if (key null || value null) throw new NullPointerException(); // 2. 计算哈希值spread() 高 16 位参与运算减少哈希冲突 int hash spread(key.hashCode()); int binCount 0; // 记录链表长度用于判断是否转红黑树 // 3. 死循环CAS 自旋重试直到插入成功 for (NodeK,V[] tab table;;) { NodeK,V f; int n, i, fh; // ------------------- 步骤1数组未初始化 → 初始化数组 ------------------- if (tab null || (n tab.length) 0) tab initTable(); // 懒加载初始化CAS 控制线程安全 // ------------------- 步骤2对应下标无节点 → CAS 直接插入 ------------------- else if ((f tabAt(tab, i (n - 1) hash)) null) { // CAS 无锁插入新节点成功则跳出循环失败则自旋重试 if (casTabAt(tab, i, null, new NodeK,V(hash, key, value, null))) break; } // ------------------- 步骤3节点正在扩容 → 帮助扩容 ------------------- else if ((fh f.hash) MOVED) tab helpTransfer(tab, f); // 多线程协同扩容提升效率 // ------------------- 步骤4下标有节点 → 加锁插入链表/红黑树 ------------------- else { V oldVal null; // 锁只锁当前链表/红黑树的头节点不锁整个数组 synchronized (f) { // 双重校验防止锁期间节点被其他线程修改 if (tabAt(tab, i) f) { // 子步骤4.1节点是链表fh 0 if (fh 0) { binCount 1; // 遍历链表 for (NodeK,V e f;; binCount) { K ek; // key 已存在 → 覆盖 value if (e.hash hash ((ek e.key) key || (key ! null key.equals(ek)))) { oldVal e.val; if (!onlyIfAbsent) // 允许覆盖则更新 e.val value; break; } NodeK,V pred e; // 遍历到链表尾部 → 追加新节点 if ((e e.next) null) { pred.next new NodeK,V(hash, key, value, null); break; } } } // 子步骤4.2节点是红黑树 else if (f instanceof TreeBin) { NodeK,V p; binCount 2; // 红黑树插入节点 if ((p ((TreeBinK,V)f).putTreeVal(hash, key, value)) ! null) { oldVal p.val; if (!onlyIfAbsent) p.val value; } } } } // ------------------- 步骤5链表过长 → 转为红黑树 ------------------- if (binCount ! 0) { if (binCount TREEIFY_THRESHOLD) // TREEIFY_THRESHOLD 8 treeifyBin(tab, i); if (oldVal ! null) // 存在旧值 → 返回旧值 return oldVal; break; } } } // 6. 元素数量 1判断是否需要扩容 addCount(1L, binCount); return null; }3. put () 核心流程总结极简版参数校验key/value 不能为 null计算哈希高 16 位参与运算减少冲突自旋循环保证并发下插入成功数组初始化第一次 put 才懒加载初始化无锁插入目标下标为空 → CAS 直接插入协助扩容节点正在扩容 → 帮助多线程扩容加锁插入目标下标有节点 →synchronized 锁头节点链表遍历覆盖 / 追加长度≥8 转红黑树红黑树树节点插入计数扩容元素数量 1判断是否扩容二、get () 方法取值全流程源码 逐行解析get()是无锁操作性能极高这也是 ConcurrentHashMap 高并发的关键1. get () 入口方法public V get(Object key) { NodeK,V[] tab; NodeK,V e, p; int n, eh; K ek; int h spread(key.hashCode()); // 计算哈希 // 数组不为空 目标下标有节点 → 继续查找 if ((tab table) ! null (n tab.length) 0 (e tabAt(tab, (n - 1) h)) ! null) { // ------------------- 步骤1头节点就是目标 key ------------------- if ((eh e.hash) h) { if ((ek e.key) key || (key ! null key.equals(ek))) return e.val; } // ------------------- 步骤2头节点 hash0 → 红黑树/扩容中 ------------------- else if (eh 0) return (p e.find(h, key)) ! null ? p.val : null; // ------------------- 步骤3遍历链表查找 ------------------- while ((e e.next) ! null) { if (e.hash h ((ek e.key) key || (key ! null key.equals(ek)))) return e.val; } } // 没找到返回 null return null; }2. get () 核心流程总结极简版计算哈希和 put 用相同的哈希算法定位下标通过(数组长度-1) 哈希值找到数组位置快速匹配头节点就是目标 key → 直接返回特殊判断hash0 → 红黑树查找 / 扩容中查找链表遍历逐个节点匹配 key找到返回值没找到返回 null全程无锁仅用tabAt()volatile 读取数组不阻塞并发性能拉满
ConcurrentHashMap 源码深度解析:put () + get () 全流程
发布时间:2026/5/16 10:08:03
ConcurrentHashMap是 Java 并发编程中线程安全、高并发的哈希表实现核心解决了HashMap线程不安全、HashTable效率低的问题。本文基于JDK 1.8源码讲解1.7 采用分段锁1.8 升级为CAS synchronized红黑树结构性能大幅提升全程结合核心源码把put存值、get取值的每一步讲透。前置核心概念必须先看懂数据结构数组 链表 红黑树链表长度 ≥ 8 且数组长度 ≥ 64 时链表转为红黑树树节点 ≤ 6 时退化为链表线程安全核心无锁CAS操作无锁更新节点有锁synchronized 锁头节点只锁链表 / 树的头节点不锁整个数组并发度极高关键常量 / 变量table哈希表数组懒加载第一次 put 才初始化sizeCtl控制数组初始化 / 扩容-1 表示正在初始化-N 表示正在扩容hashkey 的哈希值高 16 位参与运算减少哈希冲突next链表 / 树的下一个节点一、put () 方法存值全流程源码 逐行解析1. put () 入口方法public V put(K key, V value) { // 调用核心 putVal 方法onlyIfAbsentfalsekey 重复则覆盖 return putVal(key, value, false); }2. 核心 putVal () 源码逐行注释这是ConcurrentHashMap最核心的方法所有存值逻辑都在这里final V putVal(K key, V value, boolean onlyIfAbsent) { // 1. 校验key 和 value 都不允许为 null和 HashMap 不同 if (key null || value null) throw new NullPointerException(); // 2. 计算哈希值spread() 高 16 位参与运算减少哈希冲突 int hash spread(key.hashCode()); int binCount 0; // 记录链表长度用于判断是否转红黑树 // 3. 死循环CAS 自旋重试直到插入成功 for (NodeK,V[] tab table;;) { NodeK,V f; int n, i, fh; // ------------------- 步骤1数组未初始化 → 初始化数组 ------------------- if (tab null || (n tab.length) 0) tab initTable(); // 懒加载初始化CAS 控制线程安全 // ------------------- 步骤2对应下标无节点 → CAS 直接插入 ------------------- else if ((f tabAt(tab, i (n - 1) hash)) null) { // CAS 无锁插入新节点成功则跳出循环失败则自旋重试 if (casTabAt(tab, i, null, new NodeK,V(hash, key, value, null))) break; } // ------------------- 步骤3节点正在扩容 → 帮助扩容 ------------------- else if ((fh f.hash) MOVED) tab helpTransfer(tab, f); // 多线程协同扩容提升效率 // ------------------- 步骤4下标有节点 → 加锁插入链表/红黑树 ------------------- else { V oldVal null; // 锁只锁当前链表/红黑树的头节点不锁整个数组 synchronized (f) { // 双重校验防止锁期间节点被其他线程修改 if (tabAt(tab, i) f) { // 子步骤4.1节点是链表fh 0 if (fh 0) { binCount 1; // 遍历链表 for (NodeK,V e f;; binCount) { K ek; // key 已存在 → 覆盖 value if (e.hash hash ((ek e.key) key || (key ! null key.equals(ek)))) { oldVal e.val; if (!onlyIfAbsent) // 允许覆盖则更新 e.val value; break; } NodeK,V pred e; // 遍历到链表尾部 → 追加新节点 if ((e e.next) null) { pred.next new NodeK,V(hash, key, value, null); break; } } } // 子步骤4.2节点是红黑树 else if (f instanceof TreeBin) { NodeK,V p; binCount 2; // 红黑树插入节点 if ((p ((TreeBinK,V)f).putTreeVal(hash, key, value)) ! null) { oldVal p.val; if (!onlyIfAbsent) p.val value; } } } } // ------------------- 步骤5链表过长 → 转为红黑树 ------------------- if (binCount ! 0) { if (binCount TREEIFY_THRESHOLD) // TREEIFY_THRESHOLD 8 treeifyBin(tab, i); if (oldVal ! null) // 存在旧值 → 返回旧值 return oldVal; break; } } } // 6. 元素数量 1判断是否需要扩容 addCount(1L, binCount); return null; }3. put () 核心流程总结极简版参数校验key/value 不能为 null计算哈希高 16 位参与运算减少冲突自旋循环保证并发下插入成功数组初始化第一次 put 才懒加载初始化无锁插入目标下标为空 → CAS 直接插入协助扩容节点正在扩容 → 帮助多线程扩容加锁插入目标下标有节点 →synchronized 锁头节点链表遍历覆盖 / 追加长度≥8 转红黑树红黑树树节点插入计数扩容元素数量 1判断是否扩容二、get () 方法取值全流程源码 逐行解析get()是无锁操作性能极高这也是 ConcurrentHashMap 高并发的关键1. get () 入口方法public V get(Object key) { NodeK,V[] tab; NodeK,V e, p; int n, eh; K ek; int h spread(key.hashCode()); // 计算哈希 // 数组不为空 目标下标有节点 → 继续查找 if ((tab table) ! null (n tab.length) 0 (e tabAt(tab, (n - 1) h)) ! null) { // ------------------- 步骤1头节点就是目标 key ------------------- if ((eh e.hash) h) { if ((ek e.key) key || (key ! null key.equals(ek))) return e.val; } // ------------------- 步骤2头节点 hash0 → 红黑树/扩容中 ------------------- else if (eh 0) return (p e.find(h, key)) ! null ? p.val : null; // ------------------- 步骤3遍历链表查找 ------------------- while ((e e.next) ! null) { if (e.hash h ((ek e.key) key || (key ! null key.equals(ek)))) return e.val; } } // 没找到返回 null return null; }2. get () 核心流程总结极简版计算哈希和 put 用相同的哈希算法定位下标通过(数组长度-1) 哈希值找到数组位置快速匹配头节点就是目标 key → 直接返回特殊判断hash0 → 红黑树查找 / 扩容中查找链表遍历逐个节点匹配 key找到返回值没找到返回 null全程无锁仅用tabAt()volatile 读取数组不阻塞并发性能拉满