ARTICLE DETAIL

资讯详情

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

面试官问HashMap为啥线程不安全,我把源码翻给他看

面试官问HashMap为啥线程不安全,我把源码翻给他看 “HashMap是线程不安全的并发环境下用ConcurrentHashMap。”这句话几乎每个Java程序员都背得滚瓜烂熟。但面试官如果追问一句“具体哪里不安全能说说是哪个操作、什么条件下触发的不安全吗”很多人的答案就开始变得模糊了——“呃……好像是put的时候会死循环”“不对吧好像JDK 8修复了死循环”“那就是数据会丢”说实话背八股文谁都会。但如果你真的打开HashMap的源码一行一行地看你会发现那些“线程不安全”的结论其实就明明白白地写在代码里。先说结论死循环在JDK 8已经修复了“HashMap在并发put时会死循环”——这个说法在JDK 7时代确实成立。原因是JDK 7在扩容时采用头插法将旧链表的元素逐个转移到新数组时链表顺序会被反转。多线程并发扩容时两个线程同时操作同一个链表就可能形成环形链表。当get()遍历到环上时永远走不到头CPU直接飙到100%。JDK 8改用了尾插法在扩容转移时保持链表原有顺序不变环形链表的场景被彻底避免了。所以如果再拿“死循环”说事说明你的知识点还停留在JDK 7时代。真正核心的问题数据覆盖死循环修复了但HashMap在并发下仍然是“不安全的”。打开JDK 8的源码看putVal()方法的核心片段java复制下载if ((p tab[i (n 1) hash]) null) tab[i] newNode(hash, key, value, null);这行代码的意思是如果数组当前位置为空就把新节点放进去。看起来没问题对吧但问题在于这不是一个原子操作。线程A和线程B同时执行到这一步都判断出tab[i] null然后各自创建新节点并写入数组。后写入的会直接覆盖先写入的。线程A put进去的key-value对就这样凭空消失了。这不是理论推演而是真实会发生的情况。在压力测试中两个线程同时put不同的key到同一个空槽位丢失数据的概率相当高。数据覆盖不止这一处再看putVal()中处理哈希冲突的逻辑java复制下载if (e ! null) { // existing mapping for key V oldValue e.value; if (!onlyIfAbsent || oldValue null) e.value value; afterNodeAccess(e); return oldValue; }这里同样存在覆盖问题。两个线程同时put同一个key都获取到了同一个e节点然后各自更新e.value。后更新的覆盖先更新的没有任何同步保护。前一个写入的值被悄无声息地覆盖了。size也永远算不准HashMap用size字段记录键值对数量每次put或remove都会执行size或--size。同样这不是原子操作。两个线程同时put同时执行size如果初始size0两个线程各自读到0计算后各自写回1——丢了两次插入size却是1。更离谱的是如果用size()方法拿到的值偏小还可能导致扩容时机的误判——本来应该扩容了因为size偏小没触发导致链表越来越长查询性能急剧下降。扩容时同样不安全JDK 8的扩容方法resize()里有一个关键的transfer过程将老数组的元素迁移到新数组。这个过程涉及对Node.next指针的读写。如果两个线程同时触发扩容A线程修改了链表指针指向新数组B线程还拿着旧数组的引用继续操作节点的next指针就可能被写乱——倒不一定是死循环但遍历时丢失节点、get不到值是家常便饭。那ConcurrentHashMap是怎么解决的对比着看就更清楚了数据覆盖问题ConcurrentHashMap用CASsynchronized在tabAt和casTabAt中保证写入的原子性size不准问题用LongAdder思想维护baseCountCounterCell数组并发累加时分散到多个计数器上扩容问题多线程协同扩容每个线程负责一段区间用sizeCtl状态变量做全局协调总结回到面试场景。当面试官问“HashMap为什么线程不安全”与其背一句“因为并发put会导致死循环和数据丢失”不如这样回答“HashMap线程不安全主要集中在三个层面。第一put操作中的数组槽位写入和链表节点更新不是原子的会导致数据覆盖第二size字段的累加不是原子的会导致集合大小不准确进而影响扩容判断第三扩容过程中多个线程同时操作节点引用可能导致节点丢失或链表结构异常。其中JDK 7的头插法环形链表问题在JDK 8已经通过尾插法修复但数据覆盖和size不准的问题依然存在。在并发场景下应该使用ConcurrentHashMap。”然后你可以掏出手机打开JDK源码把putVal()里那几行关键代码指给他看。背结论能过面试读源码才能让人信服。
返回列表