HashMap
在多线程环境下线程不安全的原因有以下几个方面:
1. 扩容(Rehashing)时的竞争问题
当 HashMap
中的元素数量达到一定程度时(默认是容量的 75%),它会进行扩容操作。扩容时,需要将原来桶(bucket)中的数据重新分布到新的桶中,这个过程称为 rehashing。多线程情况下,多个线程同时触发扩容操作,可能导致两个或多个线程对同一个桶进行操作,从而引发数据不一致问题,甚至导致死循环或丢失元素。
2. 多线程下的 put 操作
在多线程环境下,如果多个线程同时对 HashMap
进行 put
操作,由于 HashMap
并没有同步机制,不同线程可能会同时操作同一个桶,覆盖掉其他线程正在写入的数据,导致数据丢失或冲突。
3. 链表和树结构的非原子操作
HashMap
底层存储结构是数组 + 链表/红黑树。插入数据时,如果发生哈希碰撞,多个元素会被插入到同一个桶中,形成链表或红黑树。这些插入操作并非原子操作(即使是单个元素插入都涉及到多个步骤,比如查找对应的桶,插入数据等),在多线程环境下,两个线程可能同时修改链表结构,导致链表的断裂、数据丢失或死循环。
4. 死循环问题
在 JDK 1.7 中,HashMap
使用的是链表结构,在多线程并发插入时,可能会在扩容(rehash)过程中形成环形链表。这会导致程序陷入死循环,尤其是在 get
操作中,造成 CPU 利用率飙升。
举例:
假设两个线程同时执行 put
操作:
- 线程 A 在计算桶位置并插入数据时,线程 B 也计算了同一个桶的位置并插入数据。由于操作并未同步,可能导致其中一个线程的写入被覆盖。
5. 读写操作的非原子性
多个线程同时对 HashMap
进行读写操作时,读操作和写操作不是原子的。如果一个线程正在执行 put
操作,而另一个线程执行 get
,由于没有同步控制,可能会读取到未完整插入的数据,导致返回空值或错误数据。
总结:
HashMap
的线程不安全主要原因在于没有对共享数据的访问进行同步控制,多线程环境下可能引发数据竞争、扩容时数据丢失、链表断裂或死循环等问题。如果需要在多线程环境下使用类似 HashMap
的数据结构,应该使用线程安全的实现,例如 ConcurrentHashMap
。