文章目录
- 前言
- HashMap为啥线程不安全?
- HashMap线程不安全的根本原因
- put 方法中的非原子性操作
- 扩容时的非原子性操作
- 安全的HashMap
- 总结
前言
Hi,大家好,我是王二蛋。
我们在面试的时候,经常会被问到一些有的没的、看似高深但与日常工作关系不大的问题。也因此被大家调侃为“面试造火箭,工作拧螺丝”。
今天就来探讨一个在Java面试时经常被问到的一个问题:为什么 HashMap 是线程不安全的?
HashMap为啥线程不安全?
估计HashMap听到这个问题都会骂一句:
HashMap 内心OS:我一个数据结构为啥要线程安全?
不过他既然喜欢问,咱就答给他看。
对HashMap不了解的小伙伴,先简单了解一下:HashMap本身并不具备线程安全,如果在多线程的情况下没有采取同步措施,直接对其进行修改操作,就可能会引发线程安全问题。
接下来看看,到底是什么情况下会出现线程安全问题。
HashMap线程不安全的根本原因
HashMap 的不安全主要是内部的修改不是原子操作。主要涉及以下几个操作:
- put 方法中的非原子性操作。
- 扩容时的非原子性操作。
put 方法中的非原子性操作
在 HashMap 的 put 方法中,会涉及到多个步骤,包括计算键的哈希值、找到对应的桶、处理哈希冲突等。这些步骤在没有外部同步的情况下不是原子的,所以在多线程并发时可能会出现问题。
假设有两个线程 A 和 B 同时尝试 put 同一个键到 HashMap 中:
- 线程 A 计算了哈希值并找到了对应的桶。
- 线程 B 在线程 A 还未完成 put 操作时,也计算了相同的哈希值并尝试修改同一个桶。
- 如果线程 A 在线程 B 修改桶之后才完成 put 操作,那么线程 B 的修改可能会被线程 A 的操作覆盖,从而导致数据丢失。
扩容时的非原子性操作
当 HashMap 中的元素数量超过阈值时,会触发扩容操作。扩容涉及到以下几个步骤:
- 创建一个新的数组。
- 重新计算所有键的哈希值。
- 将原数组中的元素迁移到新数组中。
这个过程也是非原子的,所以在多线程并发时也可能会出现问题。
假设有两个线程 A 和 B:
- 线程 A 检测到 HashMap 需要扩容,并开始创建新的数组。
- 线程 B 在线程 A 完成扩容之前,向 HashMap 中添加了一个新的元素。
- 线程 A 完成扩容后,可能没有考虑到线程 B 添加的新元素,从而导致数据丢失。
安全的HashMap
尽管HashMap存在线程安全问题,但在非并发修改的场景中,它依然是性能最优的键值对存储选择。
当然,如果确保线程安全,我们有其他选项。比如使用ConcurrentHashMap或通过Collections.synchronizedMap将普通HashMap转化为线程安全的Map。
总结
HashMap开发时使用率是非常高的,面试官问这个问题主要是考察对数据结构理解、对线程安全的掌握以及有没有高并发项目的经验。所以,在平时的工作中要多思考、多观察,做到知其然知其所以然。