- 一. HashMap的线程不安全问题概述
- 1. 数据覆盖导致元素丢失
- 2. 读出null值
- 3. 死循环
- 参考资料
一. HashMap的线程不安全问题概述
- 多线程下HashMap的线程不安全问题总结
| JDK7 | JDK8 |
|---|---|
| 1.数据覆盖造成的元素丢失(场景:put扩容+put扩容) 2.读出为null(场景:put时扩容+get) 3.死循环 (场景:多线程扩容时,头插法导致链表形成环,造成死循环) | 1.数据覆盖造成的元素丢失(场景:put扩容+put扩容) 2.读出为null(场景:put时扩容+get) 3.不会发生死循环(因为是尾插法) |
- HashMap 存在线程安全的本质:HashMap的"增删改"操作均是多步操作的集合,或者说是非原子的。在多线程环境下,非原子的操作可能因为线程调度的问题,引发各种线程不安全性问题。死循环和丢失更新只是其中最具代表的线程不安全表现。
JDK7和JDK8都存在这个问题
- 概述:两个线程同时执行put操作,如果计算出的索引位置相同(即哈希冲突)且该索引位置为空,则会造成前一个key被后一个key覆盖,从而导致元素丢失。
- 核心代码:put()方法
put(K key, V value);
putVal(hash(key), key, value, false, true);
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 1
tab[i] = newNode(hash, key, value, null);
else {
//...
}
if (++size > threshold)
resize();
return null;
}
- 场景解读
- 假设两个线程A、B都在进行put操作,并且通过hash计算出的插入下标相同。
- 当线程A执行完“// 1”处的代码后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入。
- 然后,线程A获得时间片,由于之前已经进行了hash冲突的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。
JDK7和JDK8都存在这个问题
- 概述:线程1进行put时,由于元素个数超出threshold而导致扩容,扩容方法里刚new出一个新的hash表,赋值给table,还没进行数据转移,此时线程2进行get,有可能导致读出为null(因为新hash表还没有值)。
- 核心源码
transient Node[] table; final Node [] resize() { //... // 使用新计算的容量new出一个新的hash表 Node [] newTab = (Node [])new Node[newCap]; // 将新创建的空hash表赋值给实例变量table table = newTab; // 1 //...将数据从旧hash表转移到新hash表 return newTab; }
- 源码解读:如果线程1执行put操作扩容,在执行完第1行之后被挂起,此时实例变量table是空的。然后线程2执行get,就会得到null值。
参考资料只有JDK7存在,JDK8不存在
- 一文教会你HashMap为啥线程不安全(多图VIP版)
- Java–HashMap–线程不安全的原因/保证线程安全的方法–方案/实例
- 常见面试题:HashMap线程不安全怎么办?
- hashmap不安全例子,arraylist不安全例子



