ConcurrentHashMap实现原理 笔记

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

主要改进

  1. 放弃分段锁,使用更细粒度的锁
  2. 引入红黑树解决哈希冲突
  3. 使用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锁桶头节点
扩容 多线程协同迁移

性能优化点

  1. 锁分离:只锁单个桶而非整个表
  2. CAS优化:无锁化读和空桶插入
  3. 红黑树:避免长链表性能下降
  4. 扩容并发:多线程协同,不停服扩容
  5. 计数优化:分散热点,避免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高并发编程的核心组件之一。

相关推荐
佳哥的技术分享6 小时前
System.setProperty vs adb setprop (Android SystemProperties)
android·adb
Railshiqian7 小时前
通过adb命令获取某个window或View/子View的绘制内容并输出为png图片的方法
android·adb·dump view
XI锐真的烦7 小时前
新手该如何选择 Android 开发框架?
android
走在路上的菜鸟8 小时前
Android学Dart学习笔记第二十六节 并发
android·笔记·学习·flutter
00后程序员张8 小时前
AppStoreInfo.plist 在苹果上架流程中的生成方式和作用
android·小程序·https·uni-app·iphone·webview
成都大菠萝8 小时前
2-2-10 快速掌握Kotlin-out协变
android
成都大菠萝8 小时前
2-2-8 快速掌握Kotlin-vararg关键字与get函数
android
成都大菠萝8 小时前
2-2-7 快速掌握Kotlin-泛型类型约束
android
城东米粉儿8 小时前
Collections.synchronizedMap()与ConcurrentHashMap的区别笔记
android