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

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

jdk1.8扩容代码

 final Node<K,V>[] resize() {
        //oldTab保存原来的table
        Node<K,V>[] oldTab = table;
        //原来数组的长度
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //原来数组的阈值 
        int oldThr = threshold;
        //新数组的阈值
        int newCap, newThr = 0;
        if (oldCap > 0) {  //这种情况下应该是数组已经被初始化了不为null
            if (oldCap >= MAXIMUM_CAPACITY) {
                //已经达到最大的初始容量了,则不扩容了,返回原来的数组
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //old<<2,数组扩大为原来的两倍,阈值也扩大为原来的两倍
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
       //省略了部分初始化数组的代码,下面的一段代码是扩容以后进
     //行扩容操作的代码
        if (oldTab != null) {
           //依次遍历数组中的元素
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //首先将桶中的值保存在e节点中
               //依次将桶中的元素置为null,
                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
                        //如果不是树节点并且有多个节点的情况下
                        // 将同一桶中的元素根据(e.hash & oldCap)是否为0进
                       //行分割,分成两个不同的链表,完成rehash

                        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值,这里只是一部分

仅是皮皮甜个人观点,如有错误,欢迎指正

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,174评论 11 349
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • Java SE 基础: 封装、继承、多态 封装: 概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽...
    Jayden_Cao阅读 2,095评论 0 8
  • 星稀月朗,墨浓天庐。流光溢彩,楼宇重重。行车流水,奔马如龙。熙熙攘攘,浮金舞绸。钢梁引架,南北飞虹。舟舸激竞,隐鳞...
    Aeroist阅读 251评论 0 1
  • 任务地址 https://bbs.excellence-girls.org/topic/209 完成时间 2016...
    TW张苗阅读 147评论 4 2