ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

HashMap为什么线程不安全?

HashMap为什么线程不安全? 一、JDK 1.7 及之前扩容死循环在 JDK 1.7 中HashMap使用头插法进行扩容resize()。当多个线程同时触发扩容时链表可能会形成环形结构导致后续的get()操作陷入死循环从而使 CPU 使用率居高不下。核心原因多线程并发 put 时同时触发了 resize 操作头插法导致链表顺序反转多个线程互相覆盖引用最终形成环。二、JDK 1.8数据丢失JDK 1.8 将扩容方法改为尾插法修复了死循环问题但线程不安全的问题依然存在主要表现为数据丢失。多线程同时执行put()时典型的丢失场景有两种1. put 覆盖丢失假设两个线程 A 和 B 同时向同一个槽位 put 元素且两者都通过哈希计算定位到了同一个链表位置线程 A 判断该位置为 null准备插入此时线程 B 也判断该位置为 null并抢先完成了插入赋值线程 A 接着也将自己的值赋值到该位置覆盖了线程 B 刚刚插入的数据2. 计数器覆盖丢失典型的多线程累加场景中Integer counter map.get(word); int newValue counter null ? 1 : counter 1; map.put(word, newValue);两个线程同时读取到相同的旧值分别 1 后写回后写的覆盖了先写的导致计数少加了一次。三、快速失败机制fail-fastHashMap的迭代器采用的是fail-fast机制。当多个线程同时对同一个HashMap进行操作时——比如一个线程在遍历另一个线程在修改结构——会立即抛出ConcurrentModificationException异常。这是因为迭代器内部维护了一个modCount计数器每次结构修改都会递增遍历时发现modCount被其他线程修改就会立即报错防止继续遍历可能导致的不确定行为。总结版本线程不安全表现主要原因JDK 1.7死循环CPU 100%扩容头插法导致链表成环JDK 1.8数据丢失、计数不准确put 操作非原子覆盖写所有版本ConcurrentModificationExceptionfail-fast 迭代器机制解决方案在多线程环境下推荐使用ConcurrentHashMap来替代 HashMap它通过 CAS synchronized 的方式保证了线程安全同时具备良好的并发性能。
返回列表