ConcurrentHashMap
HashMap 是 Java 中最常用的数据结构,但它是线程不安全的。
多线程环境下用 HashMap?轻则数据错乱,重则死循环 CPU 100%。ConcurrentHashMap 就是为并发场景设计的 HashMap。
为什么 HashMap 不是线程安全的
问题一:并发 put 导致数据覆盖
java
// 两个线程同时 put("key", value),只有一个能成功
map.put("key", valueA);
map.put("key", valueB);
// 期望:valueB
// 实际:可能还是 valueA问题二:扩容时死循环(JDK 7)
JDK 7 的 HashMap 在并发扩容时,多线程 rehash 可能导致链表成环,形成死循环:
线程A: 扩容中,rehash 链表
线程B: 同时访问,遍历到成环的链表
结果: while(true) 死循环,CPU 100%JDK 8 虽然修复了死循环问题,但并发 put 仍然不安全。
ConcurrentHashMap 的演进
| 版本 | 实现 | 特点 |
|---|---|---|
| JDK 7 | Segment + 数组 + 链表 | 锁分段,16 个 Segment |
| JDK 8+ | CAS + synchronized + 红黑树 | 锁细化,只锁单个桶 |
JDK 8 是重大升级,去掉了 Segment,直接用 CAS + synchronized:
┌──────────────────────────────────────────────────┐
│ JDK 8 ConcurrentHashMap 结构 │
├──────────────────────────────────────────────────┤
│ │
│ 数组(桶) │
│ │ │
│ ├── 链表(JDK 8: 长度 > 8 时转为红黑树) │
│ │ │
│ └── 红黑树(JDK 8+) │
│ └── 节点数 < 6 时退化为链表 │
│ │
│ 并发控制: │
│ - put: CAS + synchronized(只锁当前桶) │
│ - get: 无锁(volatile 保证可见性) │
│ │
└──────────────────────────────────────────────────┘基础用法
java
public class ConcurrentHashMapDemo {
private static final ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
public static void main(String[] args) {
// put
map.put("A", 1);
map.put("B", 2);
// get(无锁)
System.out.println("A: " + map.get("A"));
// putIfAbsent(原子操作)
map.putIfAbsent("C", 3);
map.putIfAbsent("A", 100); // 不会覆盖,因为 A 已存在
// remove(原子操作)
map.remove("A");
// replace(原子操作)
map.put("B", 2);
map.replace("B", 2, 3); // 条件替换
System.out.println("Map: " + map);
}
}原子操作
ConcurrentHashMap 提供了多个原子操作:
java
public class AtomicOperationsDemo {
private static final ConcurrentHashMap<String, AtomicInteger> map =
new ConcurrentHashMap<>();
public static void main(String[] args) {
// computeIfAbsent:原子初始化
AtomicInteger count = map.computeIfAbsent("key",
k -> new AtomicInteger());
// 原子累加
map.computeIfAbsent("requests",
k -> new AtomicInteger()).incrementAndGet();
map.computeIfAbsent("requests",
k -> new AtomicInteger()).incrementAndGet();
System.out.println("计数: " + map.get("requests").get());
}
}高性能计数:LongAdder 组合
java
public class AggregationDemo {
private static final ConcurrentHashMap<String, LongAdder> counter =
new ConcurrentHashMap<>();
public static void main(String[] args) {
// 高性能计数:多个线程同时累加
for (int i = 0; i < 1000; i++) {
counter.computeIfAbsent("pageA", k -> new LongAdder()).increment();
}
for (int i = 0; i < 500; i++) {
counter.computeIfAbsent("pageB", k -> new LongAdder()).increment();
}
counter.forEach((k, v) ->
System.out.println(k + ": " + v.sum()));
}
}这种模式在高并发统计场景下性能极高,因为 LongAdder 内部是分段累加,减少了竞争。
JDK 8 新增方法
java
public class Jdk8MethodsDemo {
private static final ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
public static void main(String[] args) {
map.put("A", 1);
map.put("B", 2);
// forEach:遍历
map.forEach((k, v) -> System.out.println(k + "=" + v));
// search:并行搜索
String result = map.search(1, (k, v) -> v > 1 ? k : null);
System.out.println("第一个值>1的键: " + result);
// reduce:并行聚合
Integer sum = map.reduce(1, (k, v) -> v, Integer::sum);
System.out.println("值的总和: " + sum);
// merge:原子合并
map.merge("C", 10, (old, newVal) -> old + newVal);
System.out.println("合并后 C: " + map.get("C"));
}
}ConcurrentHashMap vs 其他 Map
| 特性 | ConcurrentHashMap | Hashtable | Collections.synchronizedMap |
|---|---|---|---|
| 并发度 | 高(桶级锁) | 低(全局锁) | 低(全局锁) |
| null 支持 | key/value 都不允许 | 不允许 | 允许 |
| 迭代器 | 弱一致性 | 快速失败 | 快速失败 |
| get() 性能 | 无锁,高 | 加锁 | 加锁 |
弱一致性迭代器
ConcurrentHashMap 的迭代器不会抛出 ConcurrentModificationException,而是弱一致的:
java
public class IteratorDemo {
private static final ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>();
public static void main(String[] args) {
map.put("A", 1);
map.put("B", 2);
// 迭代过程中,其他线程的修改可能看到或看不到
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
// size() 可能不准确
System.out.println("size: " + map.size());
}
}常见错误
错误一:复合操作非原子
java
// ❌ 错误:get + compute 不是原子操作
Integer old = map.get("key");
Integer newVal = (old == null) ? 1 : old + 1;
map.put("key", newVal);
// ✅ 正确:使用原子方法
map.compute("key", (k, v) -> v == null ? 1 : v + 1);
// 或使用 putIfAbsent 组合错误二:key/value 用了 null
java
// ❌ 错误
map.put(null, 1); // 不允许 null key
map.put("key", null); // 不允许 null value
// ✅ 正确:使用空对象或 Optional
map.put("key", 0); // 用 0 表示不存在性能调优
初始容量
java
// 根据预估数据量设置,避免频繁扩容
ConcurrentHashMap<String, Integer> map =
new ConcurrentHashMap<>(1000); // 预估 1000 个元素并发度设置
java
// JDK 8 默认并发度是 16
// 可以根据线程数调整
ConcurrentHashMap<String, Object> map =
new ConcurrentHashMap<>(16, 0.75f, 32); // 初始16,加载因子0.75,并发度32要点回顾
- ConcurrentHashMap 是线程安全的高性能 Map
- JDK 7 用 Segment 锁分段,JDK 8 用 CAS + synchronized 锁细化
- get() 无锁,put() 只锁当前桶
- 提供了丰富的原子操作:computeIfAbsent、merge、replace 等
- key/value 不允许为 null
- 迭代器是弱一致的,不抛 ConcurrentModificationException
相关链接
- JUC 集合选择 - 根据场景选择合适的集合
- CopyOnWriteArrayList - 读多写少的 List
- 阻塞队列 - 生产者消费者场景
