外观
HashMap 为什么是不安全的
一、HashMap
线程不安全的原因
- 数据覆盖问题 当多个线程同时执行
put
操作时,若两个不同的键值对发生了哈希碰撞(即计算出的桶位置相同),可能导致其中一个线程的写入被覆盖。例如:- 线程
A
和线程B
同时尝试插入不同的键值对到同一桶中。 - 若两者均检测到当前桶为空,则可能依次插入,导致其中一个数据丢失。
- 线程
- 扩容导致的死循环(
JDK 1.7
及之前)JDK 1.7
的HashMap
在扩容时通过头插法迁移链表,多线程并发操作可能导致链表形成环形结构。后续的get
操作可能陷入死循环,CPU
占用率飙升。 - 可见性问题
HashMap
的内部状态(如size
、modCount
等字段)未使用volatile
修饰,多线程环境下可能出现脏读。 - 迭代器的快速失败(
Fail-Fast
)机制 迭代过程中若其他线程修改了HashMap
,会触发ConcurrentModificationException
。
二、实现线程安全的措施
- 使用
ConcurrentHashMap
原理:
JDK 1.7
采用分段锁(Segment
),将数据划分为多个段,每个段独立加锁,降低锁粒度。JDK 1.8
及之后改为CAS + synchronized
实现,仅对单个桶(链表头节点或红黑树根节点)加锁,并发度更高。
优点:高并发性能优异,支持安全的读写操作。
示例:
Map<String, String> map = new Concurrent`HashMap`<>();
- 使用
Collections.synchronizedMap
原理:通过包装
HashMap
,在每个方法调用时加锁(如synchronized
关键字),实现全表锁。缺点:锁粒度粗,性能较差。
示例:
Map<String, String> map = Collections.synchronizedMap(new `HashMap`<>());
- 使用
Hashtable
(不推荐)
- 原理:所有方法均用
synchronized
修饰,全表锁。 - 缺点:性能差,已逐渐被
ConcurrentHashMap
取代。
- 手动加锁(谨慎使用)
- 通过显式锁(如
ReentrantLock
)控制并发访问,但需自行管理锁逻辑,易出错。 - 示例
Map<String, String> map = new `HashMap`<>();
ReentrantLock lock = new ReentrantLock();
public void putSafe(String key, String value) {
lock.lock();
try {
map.put(key, value);
} finally {
lock.unlock();
}
}