跳转至

JDK 7 HashMap 并发死循环与丢数据

一、死循环

详见 JDK7 环形链表。头插法导致链表成环,get 时死循环。

二、丢数据

场景

两个线程同时 put 到同一个桶:

线程 A: 计算 key1 在桶 i,准备 new Entry(key1, value1, newTable[i])
线程 B: 计算 key2 也在桶 i,准备 new Entry(key2, value2, newTable[i])

过程

  1. 线程 A:newTable[i] = 旧值,设 e.next = newTable[i]。
  2. 线程 B:newTable[i] = 旧值(还是旧值!),设 e.next = newTable[i]。
  3. 线程 A:newTable[i] = e1。
  4. 线程 B:newTable[i] = e2,e2.next = newTable[i](e1)。

结果:e1 被覆盖,数据丢了。

更严重

如果两个线程都在扩容,transfer 过程中互相干扰,可能: - 整个桶的元素丢失。 - 链表成环。 - size 计数错乱。

三、为什么 HashTable / SynchronizedMap 不行

  • HashTable:方法级 synchronized,并发度低。
  • Collections.synchronizedMap:也是方法级锁,所有操作串行。

四、并发用什么

  • ConcurrentHashMap:JDK 7 分段锁,JDK 8 CAS + 桶锁。
  • 不需要并发安全但有多读:ConcurrentHashMap。
  • 读多写少:CopyOnWriteArrayList。

五、JDK 8 的 ConcurrentHashMap

放弃分段锁,改: - 空桶用 CAS 插入。 - 非空桶 synchronized 锁头节点。 - 锁粒度更细(桶级,不是段级)。

总结

HashMap 并发问题: - JDK 7:死循环 + 丢数据。 - JDK 8:不死循环了,但丢数据仍存在。 - 并发场景必须用 ConcurrentHashMap。