一、前言
在 Java 开发的高并发场景中,HashMap 因线程不安全的特性无法直接使用,而 Hashtable、 Collections.synchronizedMap 又存在并发性能瓶颈。此时, ConcurrentHashMap 作为 JUC(java.util.concurrent)包下的高性能线程安全 Map 实现,成为了并发键值对存储的首选方案。
本文将从入门视角,带你吃透 ConcurrentHashMap 的基础特性、常用 API、使用场景,以及与其他 Map 实现的核心差异,同时规避新手易踩的使用误区。
二、基本定义与核心特性
ConcurrentHashMap 是 Java 提供的高并发、线程安全的 Map 接口实现类,专门针对多线程读写场景设计,其核心特性可概括为以下 5 点:
-
**线程安全:**通过精细化的锁机制(JDK1.7 分段锁、JDK1.8 CAS + 局部 synchronized 锁)保证并发读写安全,避免了 HashMap 的并发数据错乱问题;
-
**高并发度:**锁粒度远小于 Hashtable 的全表锁,支持多线程同时操作不同存储区域,提升了并发场景下的吞吐量;
-
**不支持 null Key/Value:**为避免并发场景下的歧义( get(null) 无法区分 "Key 不存在" 和 "Value 为 null"),ConcurrentHashMap 禁止存储 null Key 和 null Value;
-
**弱一致性迭代:**迭代器采用弱一致性设计,迭代过程中允许并发修改,不会抛出 ConcurrentModificationException ,但可能读取到过期数据;
-
**兼容 Map 核心功能:**实现了 Map 接口的全部核心方法,可无缝替换 HashMap(除 null 值场景),同时扩展了 putIfAbsent 、 remove(Object, Object) 等并发专用 API。
三、与其他 Map 实现的核心差异
为明确 ConcurrentHashMap 的定位,我们对比其与 HashMap、Hashtable、 Collections.synchronizedMap 的关键差异:
| 特性维度 | HashMap | Hashtable | Collections.synchronizedMap | ConcurrentHashMap(JDK1.8) |
| 线程安全 | 不安全 | 安全(全表 synchronized) | 安全(全表 synchronized) | 安全(CAS + 桶级 synchronized) |
| 并发性能 | 单线程高效 | 极低(串行执行) | 低(全表锁竞争) | 极高(细粒度锁,支持多线程并发) |
| null 值支持 | 支持 null Key/Value | 不支持 | 同原 Map(如 HashMap 则支持) | 不支持 |
| 迭代机制 | 快速失败(抛异常) | 快速失败 | 快速失败 | 弱一致性(不抛异常) |
| 核心适用场景 | 单线程普通存储 | 低并发老旧系统 | 临时适配的小规模并发 | 高并发生产环境 |
|---|
四、常用 API 及代码示例
ConcurrentHashMap 的 API 分为 "基础存取 API" 和 "并发专用 API" 两类,以下结合 JDK1.8 版本演示核心用法:
1.初始化 ConcurrentHashMap
支持无参构造、指定初始容量、指定初始容量和并发级别(JDK1.8 中并发级别为兼容参数,无实际作用)等多种构造方式:
java
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ConcurrentHashMapInitDemo {
public static void main(String[] args) {
// 1. 无参构造,默认初始容量16,负载因子0.75
ConcurrentMap<String, Integer> map1 = new ConcurrentHashMap<>();
// 2. 指定初始容量为32
ConcurrentMap<String, Integer> map2 = new ConcurrentHashMap<>(32);
// 3. 指定初始容量32,负载因子0.8(JDK1.8支持)
ConcurrentMap<String, Integer> map3 = new ConcurrentHashMap<>(32, 0.8f);
}
}
2.基础存取与删除 API
基础 API 用法与 HashMap 类似,但底层已实现并发安全保障:
java
public class ConcurrentHashMapBasicDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> scoreMap = new ConcurrentHashMap<>();
// 1. 新增/修改元素(put方法)
scoreMap.put("张三", 90);
scoreMap.put("李四", 85);
System.out.println("初始map:" + scoreMap); // {李四=85, 张三=90}
// 覆盖已有Key的Value
scoreMap.put("李四", 88);
System.out.println("覆盖后map:" + scoreMap); // {李四=88, 张三=90}
// 2. 查询元素(get方法)
Integer zhangScore = scoreMap.get("张三");
System.out.println("张三成绩:" + zhangScore); // 90
// Key不存在时返回null(无null Key,故仅Value为null的情况不存在)
Integer wangScore = scoreMap.get("王五");
System.out.println("王五成绩:" + wangScore); // null
// 3. 删除元素(remove方法)
scoreMap.remove("李四");
System.out.println("删除李四后:" + scoreMap); // {张三=90}
}
}
3.并发专用 API
ConcurrentHashMap 扩展了多个针对并发场景的专用 API,解决了普通 Map 在并发下的原子性问题:
java
public class ConcurrentHashMapConcurrentApiDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> stockMap = new ConcurrentHashMap<>();
stockMap.put("手机", 100);
stockMap.put("电脑", 50);
// 1. putIfAbsent:Key不存在才插入(原子操作)
// 手机已存在,不生效
stockMap.putIfAbsent("手机", 150);
// 平板不存在,新增
stockMap.putIfAbsent("平板", 80);
System.out.println("putIfAbsent后:" + stockMap); // {手机=100, 电脑=50, 平板=80}
// 2. remove(Object key, Object value):键值匹配才删除(原子操作)
// 电脑库存为50时才删除
boolean isRemoved = stockMap.remove("电脑", 50);
System.out.println("电脑是否删除成功:" + isRemoved); // true
// 手机库存为200时删除(不匹配,失败)
boolean isPhoneRemoved = stockMap.remove("手机", 200);
System.out.println("手机是否删除成功:" + isPhoneRemoved); // false
System.out.println("remove后:" + stockMap); // {手机=100, 平板=80}
// 3. replace(K key, V oldValue, V newValue):旧值匹配才替换(原子操作)
boolean isReplaced = stockMap.replace("平板", 80, 90);
System.out.println("平板库存是否替换成功:" + isReplaced); // true
System.out.println("replace后:" + stockMap); // {手机=100, 平板=90}
// 4. computeIfAbsent:Key不存在时,通过函数计算Value并插入(原子操作)
stockMap.computeIfAbsent("耳机", k -> 200);
System.out.println("computeIfAbsent后:" + stockMap); // {手机=100, 平板=90, 耳机=200}
}
}
4.遍历 ConcurrentHashMap
支持 HashMap 的所有遍历方式,且迭代过程中允许并发修改:
java
import java.util.Set;
public class ConcurrentHashMapTraverseDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> userMap = new ConcurrentHashMap<>();
userMap.put("user1", 1001);
userMap.put("user2", 1002);
userMap.put("user3", 1003);
// 方式1:遍历Key集
Set<String> keySet = userMap.keySet();
for (String key : keySet) {
System.out.println(key + ":" + userMap.get(key));
}
// 方式2:遍历键值对Entry
Set<ConcurrentHashMap.Entry<String, Integer>> entrySet = userMap.entrySet();
for (ConcurrentHashMap.Entry<String, Integer> entry : entrySet) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 方式3:Lambda表达式遍历
userMap.forEach((key, value) -> System.out.println(key + ":" + value));
}
}
五、适用场景与使用误区
1. 适用场景
- **高并发读写场景:**如电商系统的库存缓存、分布式系统的配置中心、接口限流的计数器存储等;
- **需要原子性操作的场景:**如 "不存在则插入""匹配旧值才删除 / 替换" 等并发原子逻辑;
- **避免快速失败的场景:**迭代过程中需支持并发修改,且不希望因修改抛出异常的业务。
2. 新手易踩的误区
- **误区 1:认为 ConcurrentHashMap 支持 null Key/Value:**若向其中存入 null,会直接抛出 NullPointerException ,需提前做非空校验;
- **误区 2:依赖迭代器的强一致性:**其迭代器为弱一致性,只能保证最终一致性,无法实时获取最新数据,强一致性需求需额外加锁;
- **误区 3:认为所有操作都是原子的:**仅 ConcurrentHashMap 自身提供的 API 是原子的,组合操作(如 get+put )仍需手动加锁(如 putIfAbsent 可替代 "判空 + 插入" 的组合操作);
- **误区 4:忽略初始容量设置:**若提前知晓数据量,需设置合理初始容量,减少扩容次数,避免高并发下扩容带来的性能损耗。
六、基础原理初探
ConcurrentHashMap 的线程安全和高并发特性,源于其独特的锁机制设计(后续文章会深度拆解):
- JDK1.7: 采用**分段锁(Segment)**机制,将数组分为多个 Segment,每个 Segment 独立加锁,不同 Segment 的操作可并发执行;
- JDK1.8: 摒弃分段锁,采用CAS 无锁操作 + 桶级 synchronized 锁 ,进一步降低锁粒度,同时引入红黑树优化查询性能,兼顾并发安全与存取效率。
七、结语
本文完成了 ConcurrentHashMap 的入门,明确了其在高并发场景的核心定位,掌握了基础 API 和使用边界。但 ConcurrentHashMap 的核心价值在于其底层的并发安全实现,后续文章将深入拆解 JDK1.7 与 JDK1.8 的结构差异、锁机制原理及源码逻辑,带你吃透高并发 Map 的设计精髓。
下一篇文章,我们将对比 JDK1.7 分段锁与 JDK1.8 CAS + 局部锁的设计差异,揭开 ConcurrentHashMap 线程安全的底层面纱,敬请关注!