在 Java 开发中,HashMap
是最常用的键值对存储容器之一。但它本身不是线程安全的,在多线程环境下容易出现数据丢失、死循环(JDK7 扩容链表环)、读到旧数据等问题。因此,在并发场景下需要额外手段保证安全。
本文将从 Collections.synchronizedMap
和 ConcurrentHashMap
两条主线展开,结合一些零散的知识点,系统梳理 HashMap 的线程安全解决方案。
1. HashMap 为什么线程不安全?
-
并发写入数据丢失:两个线程同时 put,可能覆盖数据。
-
扩容时死循环:JDK7 链表扩容时头插法可能导致环形链表,get 操作死循环。
-
读写不一致:线程 A 写入的数据,线程 B 可能不可见。
因此,在并发场景下不能直接使用 HashMap
。
2. Collections.synchronizedMap
使用方式
Map<K, V> map = Collections.synchronizedMap(new HashMap<>());
原理
-
给所有方法加上 synchronized,保证同一时刻只有一个线程能访问。
-
等效于给整个
HashMap
加一把大锁。
优缺点
-
✅ 简单易用,适合低并发场景。
-
❌ 并发性能差,所有操作都要竞争同一把锁。
3. ConcurrentHashMap
这是并发场景下最推荐的方案。JDK7 和 JDK8 实现有较大差异。
3.1 JDK 7 实现 —— 分段锁 (Segment + ReentrantLock)
-
Segment:相当于一个小 HashMap,内部维护一个数组和链表结构。
-
分段锁:每个 Segment 一把
ReentrantLock
,不同 Segment 之间可以并发操作。 -
默认 16 个 Segment,也就是并发度为 16。
优点:比全表锁更高效。
缺点:Segment 数量固定,扩展性不足。
3.2 JDK 8 实现 —— CAS + synchronized + 桶级锁
-
取消 Segment:只保留一个
Node[] table
。 -
CAS 插入:创建新桶时用 CAS 保证线程安全。
-
synchronized 桶级锁:只对冲突的桶 (
table[i]
) 加锁,粒度比 Segment 更小。 -
链表 → 红黑树:链表过长时转为红黑树,提高查找效率。
优点:内存利用率更高,锁粒度更细,并发性能更好。
4. 锁粒度的意义
-
大粒度锁:锁住整个 Map,保证安全,但并发度低(例如 synchronizedMap、Hashtable)。
-
中等粒度锁:锁住部分区域(Segment),性能较好(JDK7 ConcurrentHashMap)。
-
小粒度锁:锁住单个桶(table[i]),最大化并发度(JDK8 ConcurrentHashMap)。
👉 核心思想:锁粒度越小,并发性能越高。
5. ReentrantLock vs synchronized
特性 | ReentrantLock | synchronized |
---|---|---|
出现时间 | JDK 1.5 并发包 | JDK 1.0 |
公平锁 | 支持公平/非公平 | 只能非公平 |
可中断锁 | 支持 | 不支持 |
可超时锁 | 支持 | 不支持 |
性能 | JDK7 前更优 | JDK8 后优化后差距缩小 |
使用方式 | 手动加解锁 | 自动加解锁 |
6. 其他保证线程安全的方式
-
Hashtable
-
JDK1.0 的线程安全 Map,所有方法都加了 synchronized。
-
性能差,几乎被 ConcurrentHashMap 替代。
-
-
手动加锁封装
private final Map<K, V> map = new HashMap<>(); public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); }
-
ReadWriteLock 封装
-
读操作共享,写操作独占。
-
适合读多写少的场景。
-
7. 总结
-
HashMap
:线程不安全。 -
Collections.synchronizedMap
:简单加锁,低并发场景可用。 -
Hashtable
:古老方案,几乎淘汰。 -
ConcurrentHashMap
:并发场景首选。-
JDK7:Segment + ReentrantLock,分段锁。
-
JDK8:CAS + synchronized,锁粒度缩小到桶级别,性能更优。
-
一句话总结:在并发编程中,不要直接用 HashMap,而是根据场景选择 synchronizedMap
或 ConcurrentHashMap
,后者是主流。