ConcurrentHashMap 详解

ConcurrentHashMap 详解

一、基本概念

ConcurrentHashMap 是 Java 并发包中提供的线程安全版本的 HashMap,它通过精细化的锁机制(JDK1.7 分段锁,JDK1.8 CAS + synchronized)实现了高并发下的线程安全。

核心特性:

  • 线程安全:支持多线程并发读写
  • 高并发:比 Hashtable 和 Collections.synchronizedMap 性能更好
  • 弱一致性:迭代器反映的是创建时的状态,不保证反映所有更新
  • 不允许 null 键值:与 HashMap 不同,键值都不能为 null

二、JDK 1.7 实现(分段锁)

1. 数据结构

// 分段数组
final Segment<K,V>[] segments;

// 段(继承ReentrantLock)
static final class Segment<K,V> extends ReentrantLock implements Serializable {
    // 每个Segment包含一个HashEntry数组
    transient volatile HashEntry<K,V>[] table;
    // ...
}

// 链表节点
static final class HashEntry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile HashEntry<K,V> next;
    // ...
}

2. 并发机制

  • 分段锁:将数据分成多个 Segment(默认16个),每个 Segment 独立加锁
  • 并发度:同时支持最多 N 个线程并发(N = Segment 数量)

3. 操作流程

  1. 定位 Segment

    Segment<K,V> segment = segments[hash & segments.length-1];
    
  2. Segment 内部操作

    • 获取锁(segment.lock()
    • 执行类似 HashMap 的操作
    • 释放锁

三、JDK 1.8 实现(CAS + synchronized)

1. 数据结构

// Node数组(类似HashMap)
transient volatile Node<K,V>[] table;

// 链表节点(与HashMap相同)
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V value;
    volatile Node<K,V> next;
    // ...
}

// 红黑树节点
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;
    // ...
}

2. 并发机制

  • CAS(Compare And Swap):无锁算法用于初始化、计数等
  • synchronized:只锁定单个桶(链表头节点/树根节点)
  • volatile:保证内存可见性

3. 关键优化

  1. 锁粒度细化:从段级别细化到桶级别
  2. 无锁化设计
    • 使用 CAS 实现无锁初始化
    • 使用 sun.misc.Unsafe 直接操作内存
  3. 扩容协助:多线程可以协同扩容

四、核心操作详解

1. put 操作流程

  1. 计算 key 的 hash 值
  2. 如果表为空,初始化表(CAS)
  3. 定位到具体桶:
    • 如果桶为空,CAS 插入新节点
    • 如果桶不为空:
      • 加锁(synchronized 桶头节点)
      • 链表处理:遍历、更新或插入
      • 树处理:调用树方法
  4. 检查是否需要转树(链表长度≥8)
  5. 检查是否需要扩容

2. get 操作流程

  1. 计算 hash 值
  2. 定位到桶
  3. 根据桶类型(链表/树)查找
    • 无锁设计,保证读性能

3. size 操作

JDK1.8 使用 CounterCell 数组(类似 LongAdder):

  • 基础计数器:baseCount
  • 竞争时分散到 CounterCell[]
  • 最终结果 = baseCount + ∑CounterCell[i]

五、并发控制机制

1. 初始化控制

// 使用sizeCtl控制初始化
private transient volatile int sizeCtl;

// 初始化逻辑
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
    try {
        // 初始化表
    } finally {
        sizeCtl = sc;
    }
}

2. 扩容机制

  • 多线程协助:当前线程处理完自己的区间后,可以协助其他线程
  • ForwardingNode:标记正在迁移的桶
  • 步长控制:每个线程处理一个区间(stride)

3. 计数机制

采用类似 LongAdder 的分段计数:

// 基础计数器
private transient volatile long baseCount;

// 计数单元数组
private transient volatile CounterCell[] counterCells;

六、与 HashMap 对比

特性HashMapConcurrentHashMap
线程安全
锁粒度无锁桶级别锁
null支持允许不允许
迭代器fail-fast弱一致
性能最高高(读接近无锁)
Java版本所有JDK1.5+

七、使用场景

  1. 高并发缓存:如商品库存缓存
  2. 共享数据存储:多线程共享配置
  3. 计数器:统计点击量等

八、代码示例

// 创建
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 安全插入
map.put("key", 1);

// 原子更新
map.compute("key", (k, v) -> v == null ? 1 : v + 1);

// 批量操作
map.forEach(2, // 并行度
    (k, v) -> System.out.println(k + "=" + v)
);

// 搜索
Integer result = map.search(2, 
    (k, v) -> v > 100 ? k : null
);

九、常见问题

1. 为什么不允许 null?

避免二义性:无法区分"不存在"和"值为null"

2. 为什么读不需要锁?

  • 使用 volatile 保证可见性
  • Node 的 value 和 next 都是 volatile

3. 如何保证线程安全?

  • 写操作:CAS + synchronized
  • 读操作:volatile 变量
  • 扩容:协作式迁移

4. JDK1.7 vs JDK1.8

版本锁粒度数据结构并发度
1.7段锁数组+链表段数
1.8桶锁数组+链表+红黑树桶数

ConcurrentHashMap 是 Java 并发编程中的重要工具,理解其实现原理有助于编写高性能的并发程序。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值