HashMap1.8中多线程扩容引起的死循环问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Hzt_fighting_up/article/details/78737468

最近在学习并发,看到书上写到hashmap在并发执行put操作时会引起死循环,因为在put中会引起扩容操作,使链表形成环形的数据结构,不是很明白,然后在网上看了一些博客,但是博客都是jdk1.7版本的,而1.8版本中的扩容操作已经和1.7版本中大不一样了,于是自己开始研究,看源码的时候,觉得jdk1.8版本中多线程put不会在出现死循环问题了,只有可能出现数据丢失的情况,因为1.8版本中,会将原来的链表结构保存在节点e中,然后依次遍历e,根据hash&n是否等于0,分成两条支链,保存在新数组中。jdk1.7版本中,扩容过程中会新数组会和原来的数组有指针引用关系,所以将引起死循环问题。

jdk1.8扩容代码

    final Node< K,V >[] resize() {
            Node< K,V >[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int oldThr = threshold;
            int newCap, newThr = 0;
            if (oldCap > 0) {
                if (oldCap >= MAXIMUM_CAPACITY) {
                    threshold = Integer.MAX_VALUE;
                    return oldTab;
                }
                //在容量不超过做大容量的时候,扩容扩大为原来的两倍
                else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                         oldCap >= DEFAULT_INITIAL_CAPACITY)
                    newThr = oldThr << 1; // double threshold
            }

           ...省略部分代码
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            //遍历旧数组中的元素,复制到table数组中
            if (oldTab != null) {
                for (int j = 0; j < oldCap; ++j) {
                    Node<K,V> e;
//在这里可能会出现数据丢失
                    if ((e = oldTab[j]) != null) {
                        oldTab[j] = null;  
                        if (e.next == null)
                            newTab[e.hash & (newCap - 1)] = e;
                        else if (e instanceof TreeNode)
                            ((TreeNode< K,V >)e).split(this, newTab, j, oldCap);
                        else { // preserve order
                            Node< K,V > loHead = null, loTail = null;
                            Node< K,V > hiHead = null, hiTail = null;
                            Node< K,V > next;
                            do {
                                next = e.next;
                                if ((e.hash & oldCap) == 0) {
                                    if (loTail == null)
                                        loHead = e;
                                    else
                                        loTail.next = e;
                                    loTail = e;
                                }
                                else {
                                    if (hiTail == null)
                                        hiHead = e;
                                    else
                                        hiTail.next = e;
                                    hiTail = e;
                                }
                            } while ((e = next) != null);
                            if (loTail != null) {
                                loTail.next = null;
                                newTab[j] = loHead;
                            }
                            if (hiTail != null) {
                                hiTail.next = null;
                                newTab[j + oldCap] = hiHead;
                            }
                        }
                    }
                }
            }
            return newTab;
        }
.

测试代码1:

public class TestHashMap {
        private static HashMap< Integer, Integer > map = new HashMap<>(2);

    public static void main(String[] args) throws InterruptedException {
        //线程1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100000; i++) {
                    int result = i;
                   new Thread(new Runnable() {
                       @Override
                       public void run() {
                           map.put(result, result);
                       }
                   }, "ftf" + i).start();
                }


            }
        });

        t1.start();


        //让主线程睡眠5秒,保证线程1和线程2执行完毕
        Thread.sleep(5000);
        for (int i= 1; i <= 100000; i++) {
            //检测数据是否发生丢失
            Integer value = map.get(i);
            if (value==null) {
                System.out.println(i + "数据丢失");
            }
        }

        System.out.println("end...");

    }
}

此时插入100000条数据,没有引起死循环和数据丢失

继续增大数据量:

此时插入100000条数据,没有引起死循环和数据丢失

继续增大数据量:数据增加到1000000,出现java.lang.OutOfMemoryError,栈内存溢出,重新调整jvm栈区内存的大小
如何调整:深入理解jvm书中写到,如果是建立过多线程导致内存溢出,在不能减少线程数量或者更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。
在idea中修改jvm参数:-Xss120k,减少一个线程分配的栈内存(默认为1m)
调整以后,成勋正常运行,并且出现数据丢失现象,但是仍没有出现死循环现象
运行结果部分:

value:null数据丢失
value:null数据丢失
value:null数据丢失
value:null数据丢失
value:null数据丢失
end...

出现了很多null值,这里只是一部分

这里写图片描述

以上仅是lz自己的观点,有错误欢迎指导讨论

猜你喜欢

转载自blog.csdn.net/Hzt_fighting_up/article/details/78737468