ConcurrentHashMap实现原理
1. 版本演进概述
| Java版本 |
主要特点 |
| Java 5-7 |
分段锁(Segment Locking) |
| Java 8+ |
CAS + synchronized + 红黑树 |
2. Java 7及之前:分段锁实现
数据结构
java
复制代码
// Java 7中的结构
ConcurrentHashMap
├── Segment[] segments (继承ReentrantLock)
├── HashEntry[] table
├── HashEntry (链表节点)
└── HashEntry
分段锁机制
- 将整个哈希表分成多个段(Segment)
- 每个Segment独立加锁
- 默认16个Segment,支持16个线程并发写
java
复制代码
// Java 7简化的伪代码
public class ConcurrentHashMap<K, V> {
final Segment<K,V>[] segments;
static final class Segment<K,V> extends ReentrantLock {
transient volatile HashEntry<K,V>[] table;
// ...
}
static final class HashEntry<K,V> {
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
public V put(K key, V value) {
int hash = hash(key);
// 1. 定位到哪个Segment
Segment<K,V> s = segmentForHash(hash);
// 2. 对该Segment加锁
s.lock();
try {
// 3. 在Segment内部操作
return s.put(key, hash, value, false);
} finally {
s.unlock();
}
}
}
3. Java 8及之后:CAS + synchronized
主要改进
- 放弃分段锁,使用更细粒度的锁
- 引入红黑树解决哈希冲突
- 使用CAS实现无锁化读取和部分写操作
核心数据结构
java
复制代码
// Java 8中的Node结构
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
// ...
}
// 树节点(当链表长度≥8时转化为红黑树)
static final class TreeNode<K,V> extends Node<K,V> {
TreeNode<K,V> parent; // 红黑树链接
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // 删除时需要取消链接
boolean red;
}
// 转发节点(扩容时使用)
static final class ForwardingNode<K,V> extends Node<K,V> {
final Node<K,V>[] nextTable;
// ...
}
4. 核心操作原理
4.1 初始化
java
复制代码
// 延迟初始化:table在第一次插入时创建
public ConcurrentHashMap() {
// 空构造函数,table为null
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // 正在初始化
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // CAS设置状态为-1
try {
// 初始化table
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
table = tab = (Node<K,V>[])new Node<?,?>[n];
sc = n - (n >>> 2); // 0.75*n,扩容阈值
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
4.2 put操作流程
java
复制代码
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 1. 参数检查
if (key == null || value == null) throw new NullPointerException();
// 2. 计算hash
int hash = spread(key.hashCode());
int binCount = 0;
// 3. 循环直到插入成功
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 3.1 表为空则初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 3.2 计算桶位置,如果为空则CAS插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // CAS成功,插入完成
}
// 3.3 如果正在扩容,则帮助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 3.4 正常插入
else {
V oldVal = null;
synchronized (f) { // 对桶的头节点加锁
// 再次检查节点是否变化
if (tabAt(tab, i) == f) {
if (fh >= 0) { // 链表
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 找到相同key则更新
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 遍历到链表末尾则插入
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
else if (f instanceof TreeNode) { // 红黑树
Node<K,V> p;
binCount = 2;
// 红黑树插入
if ((p = ((TreeNode<K,V>)f).putTreeVal(hash, key, value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 3.5 检查是否需要树化
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD) // 链表长度≥8
treeifyBin(tab, i); // 可能转为红黑树
if (oldVal != null)
return oldVal;
break;
}
}
}
// 4. 增加计数,可能触发扩容
addCount(1L, binCount);
return null;
}
4.3 get操作(无锁读取)
java
复制代码
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// 1. 计算hash
int h = spread(key.hashCode());
// 2. 定位桶位置
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 3. 检查头节点
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 4. hash为负表示特殊节点(树节点或转发节点)
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 5. 遍历链表
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
5. 扩容机制
多线程协同扩容
java
复制代码
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// 1. 计算每个线程处理的桶区间
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // 最小16个桶
// 2. 创建新数组(2倍大小)
if (nextTab == null) {
try {
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n; // 从后往前迁移
}
// 3. 多线程协同迁移
// 每个线程领取自己的迁移任务区间
// 使用ForwardingNode标记已迁移的桶
}
6. 计数机制(size())
LongAdder思想的应用
java
复制代码
// 使用CounterCell数组分散计数
@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}
// 计数更新
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 1. 尝试通过CAS更新baseCount
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
// 2. CAS失败,使用CounterCell
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
// 3. 创建或扩容CounterCell数组
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount(); // 汇总所有计数
}
// 4. 检查是否需要扩容
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// 触发扩容...
}
}
}
7. 关键特性总结
并发控制策略
| 操作 |
并发策略 |
| 读操作 |
完全无锁(volatile读) |
| 空桶插入 |
CAS操作 |
| 非空桶操作 |
synchronized锁桶头节点 |
| 扩容 |
多线程协同迁移 |
性能优化点
- 锁分离:只锁单个桶而非整个表
- CAS优化:无锁化读和空桶插入
- 红黑树:避免长链表性能下降
- 扩容并发:多线程协同,不停服扩容
- 计数优化:分散热点,避免CAS竞争
内存可见性保证
java
复制代码
// 通过Unsafe类保证原子操作
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
8. 与HashMap的对比
| 特性 |
HashMap |
ConcurrentHashMap (Java 8) |
| 线程安全 |
否 |
是 |
| 锁机制 |
无 |
CAS + synchronized |
| 数据结构 |
数组+链表/红黑树 |
数组+链表/红黑树 |
| 扩容 |
单线程 |
多线程协同 |
| 迭代器 |
fast-fail |
弱一致性 |
| null支持 |
允许key/value为null |
不允许 |
9. 使用建议
java
复制代码
// 1. 创建
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 2. 使用原子操作(推荐)
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
// 3. 并行操作
map.forEach(1, (k, v) -> System.out.println(k + ": " + v));
// 4. 搜索
String result = map.search(1, (k, v) -> v > 100 ? k : null);
ConcurrentHashMap通过精细的锁设计、CAS操作、多线程协同等机制,在保证线程安全的同时提供了接近非并发容器的性能,是现代Java高并发编程的核心组件之一。