跳转到内容

Concurrenthashmap实现

来自代码酷

模板:Note

概述[编辑 | 编辑源代码]

ConcurrentHashMap是Java集合框架中线程安全的哈希表实现(位于`java.util.concurrent`包),专为解决多线程环境下`HashMap`的线程不安全问题和`Hashtable`的性能瓶颈而设计。其主要特点包括:

  • 分段锁技术(JDK 1.7)或CAS+synchronized优化(JDK 1.8+)
  • 高并发读操作完全无锁
  • 迭代器弱一致性(不会抛出`ConcurrentModificationException`)

版本演进对比[编辑 | 编辑源代码]

JDK版本实现差异
版本 核心机制 锁粒度
JDK 1.7 分段锁(Segment) 段级别
JDK 1.8+ CAS + synchronized 桶级别(Node)

实现原理[编辑 | 编辑源代码]

JDK 1.7 分段锁设计[编辑 | 编辑源代码]

通过将数据划分为多个Segment(默认16个),每个Segment独立加锁,不同Segment的操作可并行执行。

graph LR A[ConcurrentHashMap] --> B[Segment 0] A --> C[Segment 1] A --> D[...] B --> E[HashEntry数组] C --> F[HashEntry数组] style B stroke:#f9f,stroke-width:4px style C stroke:#0f0,stroke-width:4px

JDK 1.8 优化方案[编辑 | 编辑源代码]

改用更细粒度的桶级别锁: 1. 使用CAS实现无锁化插入 2. 仅当哈希冲突时,对链表头节点或红黑树根节点加`synchronized`

// JDK 1.8 putVal方法核心逻辑(简化版)
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            synchronized (f) {
                // 链表或红黑树插入逻辑
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

关键特性[编辑 | 编辑源代码]

线程安全保证[编辑 | 编辑源代码]

  • 写操作:JDK 1.8后使用CAS+`synchronized`双重保障
  • 读操作:直接访问volatile修饰的`Node.val`,无锁
  • 扩容:协助转移机制(多线程共同完成扩容)

一致性模型[编辑 | 编辑源代码]

  • 键值对访问保证happens-before原则
  • 迭代器反映创建时的状态(可能不包含后续修改)

性能优化技巧[编辑 | 编辑源代码]

初始化参数建议[编辑 | 编辑源代码]

解析失败 (语法错误): {\displaystyle 初始容量 = \frac{预估元素数量}{并发级别} \times 负载因子(0.75) }

// 推荐初始化方式(预估1000个元素,8线程并发)
ConcurrentHashMap<String, Integer> map = 
    new ConcurrentHashMap<>(1000/8*4/3, 0.75f, 8);

原子复合操作[编辑 | 编辑源代码]

// 线程安全的累加操作
map.compute("counter", (k, v) -> (v == null) ? 1 : v + 1);

// 合并插入示例
map.merge("key", 1, Integer::sum);

典型应用场景[编辑 | 编辑源代码]

缓存系统[编辑 | 编辑源代码]

class CacheManager {
    private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
    
    public Object get(String key) {
        return cache.computeIfAbsent(key, k -> {
            // 模拟数据库查询
            return fetchFromDatabase(k);
        });
    }
}

计数器组[编辑 | 编辑源代码]

// 多线程事件统计
ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap<>();

void recordEvent(String eventType) {
    counters.computeIfAbsent(eventType, k -> new AtomicLong()).incrementAndGet();
}

常见问题[编辑 | 编辑源代码]

为什么不允许null值?[编辑 | 编辑源代码]

  • 设计上无法区分「键不存在」与「键映射到null」两种状态
  • 避免并发场景下的歧义问题

与Collections.synchronizedMap区别?[编辑 | 编辑源代码]

并发Map实现对比
特性 ConcurrentHashMap SynchronizedMap
锁粒度 桶级别 全表锁
并发读 完全无锁 需要获取锁
迭代器 弱一致性 快速失败

性能测试数据[编辑 | 编辑源代码]

以下为4线程环境下的吞吐量对比(ops/ms):

barChart title 并发Map吞吐量对比 x-axis 实现类 y-axis 操作数/ms series 写入 series 读取 series 混合 ConcurrentHashMap: 写入=1200, 读取=8500, 混合=4800 Hashtable: 写入=350, 读取=400, 混合=380 SynchronizedMap: 写入=320, 读取=420, 混合=390

模板:Tip

参见[编辑 | 编辑源代码]