HashMap的线程不安全(jdk8也会造成死循环,原因暂未查明)

HashMap

基本概念

            HashMap在jdk7及以前由数组加链表实现,在jdk8中加入了红黑树,在链表长度到8的时候转换为红黑树,降到6时还原成链表。默认初始化数组容量是16,每次扩容是2的次幂,默认加载因子是0.75

           众所周知,HashMap是线程不安全的,至于怎么个不安全法,还是要代码跑一下,看看效果,才有感觉。



HashMap线程不安全测试代码

           代码中启动了两个线程共对map进行一共100000存入数据。

public class HashMapTestThread {


    static Map<String,String> map = new HashMap<String,String>();


    public static class AddThread implements Runnable{

        int start=0;

        public AddThread(int start){

            this.start = start;

        }

        @Override
        public void run(){
            for (int i = start; i < 100000; i+=2) {
                map.put(Integer.toString(i),Integer.toBinaryString(i));
            }
        }

    }

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(new HashMapMultiThread.AddThread(0));
        Thread t2 = new Thread(new HashMapMultiThread.AddThread(1));
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(map.size());
    }


}

大致运行结果可能有三种:

1.程序正常结束,结果正确,map.siz()结果为100000(跑了几次没有一次成功)

2.程序正常结束,结果小于100000(大概率是这种情况出现),如下图:

在这里插入图片描述

3.程序结束不了,电脑以肉眼可见的速度变烫,此时不用说也知道死循环了,造成死循环的原因,jdk7因为扩容时头插法造成的链表成环,jdk8下解决了这个问题,但依旧会造成死循环,原因暂时不清楚。





结果2数据少了,错误原因分析


           数据少了原因十分简单,两个线程操作同一个节点时,前一个线程的操作被后一个线程覆盖掉了。比如:线程1有个A节点hash之后要插在数组4号位后边,线程2有个B节点hash之后也要插在数组4号位后边,正确结果应该是4->A->B,或者4->B->A,但是如果1和2同时访问4号节点,此时4号节点即没来的及添加A,也没来得及添加B,所以1和2都是看到4是空的,开始在4后边添加,线程1手快添了上去,线程2手慢,于是线程1的修改就被线程2覆盖掉了,变成了4->B,线程1的修改就A丢了。



结果3死循环,错误原因分析


           因为结果3中程序一直无法结束,电脑又逐渐发热,所以用top命令查看cpu运行情况,可以看到pid1977已经cpu200%在跑了,如下图:

HashMap的线程不安全(jdk8也会造成死循环,原因暂未查明)_第1张图片

然后,jstack打印其堆栈信息的,jdk7下运行如下图:


图中可以看出:两个线程都在HashMap.put里出不来了,定位到HashMap的494行:

HashMap的线程不安全(jdk8也会造成死循环,原因暂未查明)_第2张图片
           这个地方看上去是两个线程在遍历HashMap内部数据,此时由于链表已经成环,上述遍历就成了一个死循环。

           jdk7中死循环的成因是两个线程同时扩容rehash时,造成了链表成环。

           jdk7的rehash时,使用的是头插法,迁移之后链表顺序会变,即A->B->C变成了C->B->A,此时,如果线程1和2同时扩容,均创立临时节点保存了A->B,准备对此操作,此时线程1继续操作,线程2暂时,线程1将其变为了C->B->A,此时真实的B节点是指向A的,然后线程2继续执行,根据临时节点的A->B继续操作,安置好A之后,去已经被线程1安置好的真实节点中拿下一轮操作节点,即B->A继续操作,让B指向原本安置好的A,然后继续拿下一个节点A继续操作,再让A指向B,就造成了A指向B,B指向A的环形链表。(此处大概介绍,详细介绍请翻阅 https://www.jianshu.com/p/1e9cf0ac07f4)

           jdk8中不再采用头插法,而是采用了尾插法,插入之后链表的顺序不变,从而避免了这个地方的链表成环,但有了新的死循环原因。



jdk8运行下如下图:

HashMap的线程不安全(jdk8也会造成死循环,原因暂未查明)_第3张图片
jdk8下也会发生死循的问题,定位的代码到了1824行:HashMap的线程不安全(jdk8也会造成死循环,原因暂未查明)_第4张图片
具体原因暂未查明啊,看样子是红黑树成环了…

你可能感兴趣的:(java)