关于JDK7HashMap多线程场景下扩容死循环问题的原因以及JDK8的解决方案

JDK7中HashMap扩容出现死循环的本质原因是,这个集合不是线程安全的,共享变量Node结点访问出错,当前线程在扩容到槽中最后一个节点B时,由于其他线程将最后一个节点B的next引用置成了A(下图),被当前线程看到了(实际情况是,没有同步措施的情况下,当前线程可能看到,可能看不到,和线程何时将自己本地内存的值刷新到主存有关,这里涉及JMM模型和内存可见性,如有不懂请百度),导致了当前线程多执行了一次循环体,在循环体内将本不应该出现的结点A的next指向了槽点头B,导致生成环。
其他线程扩容过程:
关于JDK7HashMap多线程场景下扩容死循环问题的原因以及JDK8的解决方案_第1张图片
当前线程扩容过程:
关于JDK7HashMap多线程场景下扩容死循环问题的原因以及JDK8的解决方案_第2张图片

//JDK7的扩容时 转移结点的源码    
void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
            while(null != e) {
                Entry<K,V> next = e.next; //(正常的最后一次循环),B的next本为null,但是其他线程修改成了A,被当前线程错误的观察到了
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];   //(因为错误的可见性导致的多余的一次循环)将A的next再次指向槽点头B,导致成环
                newTable[i] = e;
                e = next;               //(正常的最后一次循环)这将导致多进行一次循环
            }
        }
    }

JDK7由于在其他线程扩容时的搬运结点操作为了提高效率采用的头插法导致的当前线程不小心看到了错误的next指向,导致成环,JDK8采用尾插法(尾插法一是用于避免死循环,二是根据槽中个数判断是否需要树化)修复了此问题,但是由于是线程不安全的,还会有别的并发问题发生。

你可能感兴趣的:(后端,HashMap)