JDK 7 HashMap 并发死循环与丢数据¶
一、死循环¶
详见 JDK7 环形链表。头插法导致链表成环,get 时死循环。
二、丢数据¶
场景¶
两个线程同时 put 到同一个桶:
线程 A: 计算 key1 在桶 i,准备 new Entry(key1, value1, newTable[i])
线程 B: 计算 key2 也在桶 i,准备 new Entry(key2, value2, newTable[i])
过程¶
- 线程 A:
newTable[i] = 旧值,设 e.next = newTable[i]。 - 线程 B:
newTable[i] = 旧值(还是旧值!),设 e.next = newTable[i]。 - 线程 A:newTable[i] = e1。
- 线程 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。