HashMap为什么线程不安全

博客主要阐述了HashMap的线程安全问题,包括线程1调用contains()后get(),同时线程2调用remove();两个线程执行put操作时可能出现数据覆盖;rehash时会产生循环链表,导致get操作出现死循环。

HashMap的线程安全问题体现在以下3个方面:

1、线程1调用contains()返回true,然后调用get(),同时线程2调用remove()

2、2个线程执行put操作: 线程1put()时,记录了头结点为node1,这时时间片用完,线程2put(),且把数据插在了链表的头部,完成put操作。线程1接着完成put()剩余的操作,这时新的头结点已经变了,但是线程1记录的旧的头结点,把数据插入到头结点,覆盖了线程2put的数据,导致线程不安全。

3、rehash的时候,会产生循环链表,get的时候,死循环。
在这里插入图片描述

HashMap 线程安全的主要原因在于没有对共享数据的访问进行同步控制,多线程环境下可能引发数据竞争、扩容时数据丢失、链表断裂或死循环等问题。具体如下: - **put 操作覆盖数据**:JDK1.8 中,多线程HashMap 进行 put 操作,调用 `HashMap#putVal()` 时,若两个线程 A、B 都在进行 put 操作,且 hash 函数计算出的插入下标相同。当线程 A 执行完部分代码后因时间片耗尽被挂起,线程 B 得到时间片后在该下标处插入元素,完成正常插入,接着线程 A 获得时间片,由于之前已进行 hash 碰撞判断,此时再判断,直接插入,导致线程 B 插入的数据被线程 A 覆盖,从而造成线程安全 [^4]。 - **addEntry 操作数据丢失**:在 hashmap 做 put 操作调用相关方法时,若 A 线程和 B 线程同时对同一个数组位置调用 `addEntry`,两个线程会同时得到当前的头结点,然后 A 写入新的头结点之后,B 也写入新的头结点,B 的写入操作会覆盖 A 的写入操作,造成 A 的写入操作丢失 [^5]。 - **扩容问题**:HashMap 的扩容机制是重新申请一个容量为当前 2 倍的桶数组,将原先的记录逐个重新映射到新的桶里,并将原先的桶逐个置为 null 使引用失效。HashMap 线程安全问题就出在扩容(resize)这里,多线程环境下可能导致数据丢失、链表断裂或死循环等情况 [^3]。 ### 示例代码 以下是一个简单示例,展示多线程环境下 HashMap线程安全性: ```java import java.util.HashMap; import java.util.Map; public class HashMapThreadUnsafeExample { private static Map<Integer, Integer> hashMap = new HashMap<>(); public static void main(String[] args) throws InterruptedException { // 创建两个线程 Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { hashMap.put(i, i); } }); Thread thread2 = new Thread(() -> { for (int i = 1000; i < 2000; i++) { hashMap.put(i, i); } }); // 启动线程 thread1.start(); thread2.start(); // 等待两个线程执行完毕 thread1.join(); thread2.join(); // 输出 map 的大小 System.out.println("Map size: " + hashMap.size()); } } ``` 在上述代码中,两个线程同时向 HashMap 中添加元素,由于 HashMap 线程安全,可能导致最终输出的 map 大小小于 2000。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值