ConcurrentHashMap学习

ConcurrentHashMap 是 Java 并发编程中极其重要且常用的数据结构。它是线程安全的哈希表实现,专为高并发场景设计,相比全局加锁的 Hashtable,它通过更细粒度的锁机制(JDK 1.7 的分段锁,以及 JDK 1.8 之后的 CAS + synchronized)实现了极高的并发读写性能。

以下是 ConcurrentHashMap 的核心使用指南和最佳实践:

一、 基础使用与初始化

ConcurrentHashMap 的基本 API 与 HashMap 类似,但有几个关键区别需要注意:

  1. 不允许 null :它的 Key 和 Value 都不允许为 null ,如果传入会直接抛出 NullPointerException
  2. 预设容量:如果在创建时能预估数据规模,建议指定初始容量,这可以有效减少运行过程中的 rehash(扩容)次数,提升性能。
java 复制代码
// 1. 默认初始化
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

// 2. 指定初始容量(推荐,减少扩容开销)
ConcurrentHashMap<String, Integer> mapWithCapacity = new ConcurrentHashMap<>(32);

二、 核心原子操作方法(重点)

在多线程环境下,传统的"先检查再更新"(Check-then-Act)操作是不安全的。ConcurrentHashMap 提供了一系列原子性的复合操作方法,强烈建议优先使用这些方法来简化代码并保证线程安全:

1. putIfAbsent(key, value)

如果指定的键不存在,才插入键值对。常用于实现简单的缓存或防止重复初始化。

java 复制代码
// 只有当 "user_1" 不存在时,才会放入数据
map.putIfAbsent("user_1", 100); 

2. computeIfAbsent(key, mappingFunction)

如果键不存在,则通过传入的函数计算出一个值并放入 Map。这是一个极其优雅的懒加载实现方式,且函数内部只会执行一次。

java 复制代码
// 如果 "user_2" 不存在,则计算其分数并放入
map.computeIfAbsent("user_2", key -> calculateScore(key));

3. compute / merge (高频更新场景)

当需要对已存在的值进行更新时(如计数器),使用这两个方法可以避免多次加锁。

java 复制代码
// 优雅地实现单词计数:如果存在则相加,不存在则设为 1
map.merge("word", 1, Integer::sum); 

三、 安全的遍历与状态获取

在并发环境下,普通的 size() 方法或 entrySet 遍历可能因为其他线程的修改而变得不准确,甚至抛出异常。ConcurrentHashMap 提供了专门的应对机制:

  1. 弱一致性遍历 :使用 forEachsearchreduce 等专用方法进行遍历。这些方法不仅线程安全,而且不会抛出 ConcurrentModificationException,但它们反映的可能是某一时刻的近似状态。
  2. 精确获取大小 :如果业务强依赖集合的精确大小,应使用 mappingCount() 方法代替 size(),它返回 long 类型,在并发下更加精确。
java 复制代码
// 安全的并发遍历
map.forEach((key, value) -> {
    System.out.println(key + ": " + value);
});

// 获取更精确的元素数量
long count = map.mappingCount();

四、 极致性能优化:LongAdder

在诸如"单词计数"、"接口限流统计"等高频更新 的场景下,即使使用了 ConcurrentHashMap,多线程同时 CAS 更新同一个 Key 对应的值依然会产生竞争。此时,可以将 Value 设为 LongAdder 来极致优化:

java 复制代码
ConcurrentHashMap<String, LongAdder> wordCounts = new ConcurrentHashMap<>();

// 结合 computeIfAbsent 和 LongAdder 实现无锁化高频累加
wordCounts.computeIfAbsent("hello", k -> new LongAdder()).increment();

五、 核心注意事项总结

  • 严禁外部加锁 :不要使用 synchronized(map) 来包裹 ConcurrentHashMap 的操作,这会破坏其内部的细粒度并发优势,使其退化为串行执行。
  • 迭代器是弱一致的:在遍历过程中,如果其他线程修改了 Map,迭代器不会报错,但也不保证能遍历到最新修改的数据。
  • 底层演进 :JDK 1.8 之后,它摒弃了 1.7 时代的 Segment 分段锁,全面采用 Node数组 + 链表 + 红黑树 结构,结合 CAS + synchronized 锁住单个桶的头节点,并发度理论上最高可达数组长度。
相关推荐
折哥的程序人生 · 物流技术专研1 小时前
《Java 100 天进阶之路》第53篇:volatile与JMM(2026版)
java·volatile·内存屏障·可见性·java内存模型·jmm·java100天进阶
米饭不加菜1 小时前
使用万用表判断三极管(BJT)好坏
java·开发语言
zzzll11111 小时前
LangChain实战-RAG问答机器人
学习·计算机·程序员·大模型
北鸟南游2 小时前
学习-claude code 工程化实战的笔记-极客时间
笔记·学习·ai编程
Maynor9962 小时前
AI Coding 零基础实战教程|第五部分:完整项目案例实操
java·前端·人工智能·claude code·ai coding
心中有国也有家2 小时前
AtomGit Flutter 鸿蒙客户端:呼吸练习的三种科学模式
学习·flutter·华为·harmonyos
三品吉他手会点灯2 小时前
嵌入式机器学习 - 学习笔记1.0.2 - 学习路线导览:从机器学习基础到 TinyML应用
人工智能·笔记·嵌入式硬件·学习·机器学习
生戎马2 小时前
Tomcat Container容器之Engine:StandardEngine _
java·tomcat
zhangjw342 小时前
第29篇:Java伪共享与对象分配:并发性能优化的关键
java·开发语言·性能优化