Java基础、中级、高级、架构面试资料

从 HashMap 的扩容机制来说它为什么不是线程安全的!

JAVA herman 2482浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog2,发送下载链接帮助你免费下载!
本博客日IP超过2000,PV 3000 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog2,之前的微信号好友位已满,备注:返现
受密码保护的文章请关注“业余草”公众号,回复关键字“0”获得密码
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领
腾讯云】1核2G5M轻量应用服务器50元首年,高性价比,助您轻松上云

我在前面的文章《Java 线程安全的3大核心:原子性、可见性、有序性》中已经说到了什么是线程安全!根据这篇文章,我们对照着 HashMap 来说说它为什么不是线程安全的?

前面我也强调过多次,回答是不是线程安全的请从:原子性、可见性、有序性(有的说顺序性,其实是一个意思)3 个方面来回答。参考着 3 个特性指标来说,HashMap 的 put、get、remove等没有一个是线程安全的。

下面我们通过一个例子来说说 HashMap 在并发多线程环境中使用是如何可能造成死循环问题的!

public class HashMapInfiniteLoopTest {  
    // 业余草:www.xttblog.com
    private static HashMap<Integer,String> map = 
        new HashMap<Integer,String>(2,0.75f);  
    public static void main(String[] args) {  
        map.put(5, "C");  
        new Thread("Thread1") {  
            public void run() {  
                map.put(7, "B");  
                System.out.println(map);  
            };  
        }.start();  
        new Thread("Thread2") {  
            public void run() {  
                map.put(3, "A);  
                System.out.println(map);  
            };  
        }.start();        
    }  
}

需要说明的是,这个例子我是在 JDK 1.7 环境下测试的!

现在我对上面的代码,做一个简单的解读。

首先,初始化为一个长度为 2 的 HashMap,loadFactor=0.75,threshold=2*0.75=1,也就是说当 put 第二个 key 的时候,我们的 map 就需要进行 resize(扩容)。

为了快速说明线程安全问题,我们通过设置断点让线程1和线程2同时 debug 到 put 方法中调用的 transfer 方法的首行。

这时两个线程已经成功添加数据。放开 thread1 的断点至 transfer 方法的“Entry next = e.next;” 这一行;然后放开线程2的的断点,让线程2进行 resize。

我用一张图片来总结一下这个过程!

JDK 1.7 HashMap 引发的线程安全问题

根据上图再结合源代码以及运行的案例得知,Thread1 的 e 指向了key(3),而 next 指向了 key(7),其在线程二 rehash 后,指向了线程二重组后的链表。

线程一被调度回来执行,先是执行 newTalbe[i] = e, 然后是 e = next,导致了 e 指向了 key(7),而下一次循环的 next = e.next 导致了 next 指向了 key(3)。

JDK1.7 多线程环境HashMap引发的死循环问题

e.next = newTable[i] 导致 key(3).next 指向了 key(7)。这时的 key(7).next 已经指向了 key(3),于是环形链表就这样形成。

HashMap 引发的死循环问题

环形链表形成之后,如果后面有 get(1) 的操作,即取出这个元素的操作,就会发生 Infinite Loop(死循环)问题。

为什么会发生线程安全问题?

很简单,HashMap 的所有操作都没有保证线程安全的核心 3 要素,即:原子性、可见性、顺序性。所以它发生线程安全问题,我一点也不惊讶!

上面这个例子很简单,一点也不复杂,如果你看不懂,建议你操作我的操作,对照说明,一步一步调试。

最后,我在留一个问题,HashMap 初始化后,在没有任何元素的情况下,remove(null) 会报错吗?为什么?

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加作者微信号:xttblog2。备注:“1”,添加博主微信拉你进微信群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作也可添加作者微信进行联系!

本文原文出处:业余草: » 从 HashMap 的扩容机制来说它为什么不是线程安全的!