Java基础篇-HashMap多线程问题

常会说到HashMap在多线程下是不安全的,那么不安全会引起什么问题呢?

多线程下,对一个HashMap进行修改时,会造成元素丢失或者链表闭环。

1、HashMap的存储结构

首先看下HashMap的存储结构,HashMap的存储结构是Entry数组+链表的结构,如下图

2、先说一下元素丢失是怎么引起的

       上图中,两个线程分别插入元素g和h,经过hash计算,插入位置都是数组索引为3的链表中,g和h分别将到f的next指向自身(JDK8版本),g和h分别拿到尾结点f,g先将f的next指向g,接着h又将f的next做了修改指向h。最终,g节点丢失,f的next指向h。

       这是多线程下使用临界资源经常面临的一个问题,很容易理解。

3、那链表闭环又是怎么引起的呢,往链表中添加元素为什么会引起链表闭环呢?

    这需要先从HashMap的扩容说起了。

    HashMap是为了快速查询而存在的容器,为了能够快速查询,采用了通过空间换时间的方法。HashMap为了方便查询,使用了Hash处理,快速定位元素数组所在下标位置。常规状态下HashMap中数组长度永远大于元素个数的。

那么问题来了,数组长度是不可变的,如果元素数量超过loadFactor*capacity的数值,那么HashMap就需要重新申请一个新的数组,并把原来存储的元素转移到新的数组中。数组的长度变了,就需要重新计算元素的Hash位置。(不了解这块知识点的可以打开链接,看一下HashMap中Hash计算和Hash碰撞的问题)。


扩容这个过程中发生了什么呢,让我们一块看一下resize这个函数

void resize(int newCapacity) {

        Entry[] oldTable = table;

        int oldCapacity = oldTable.length;

        if (oldCapacity == MAXIMUM_CAPACITY) {

            threshold = Integer.MAX_VALUE;

            return;

        }

        Entry[] newTable = new Entry[newCapacity];

        transfer(newTable, initHashSeedAsNeeded(newCapacity));

        table = newTable;

        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);

    }

    /**

    * Transfers all entries from current table to newTable.

    */

    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;

                if (rehash) {

                    e.hash = null == e.key ? 0 : hash(e.key);

                }

                int i = indexFor(e.hash, newCapacity);

                e.next = newTable[i];

                newTable[i] = e;

                e = next;

            }

        }

    }

resize这个函数很容易理解,就是申请了一个新的Entry数组newTable,进行空间扩容,然后接下来调用transfer 函数,将原来的数组中的链表节点元素,排列到新的Entry数组中。

在多线程下这个过程为什么会出现链表闭环呢?两个线程一起调用resize,分别对HashMap进行扩容。继续以这个图作为例子

假设HashMap现在容量是32,进行扩容到64,线程A和B都先申请一个新的64长度的数组,然后移动原来的元素,A先移动了f和g放到索引是n的位置,f的next指向g,B将元素f,g也放到索引是n的位置,不过是将g的next指向f。

那么现在出问题了,f的next指向g,g的next指向f,扩容完成。查询h元素,经过Hash计算,找到索引为n的数组元素,遍历链表查找f然后next操作一直在g和f之间循环,进入闭环链表中了,HashMap查询出问题了。

注意:JDK8以后HashMap使用红黑树,已经不会出现闭环的问题了,不过元素丢失还是没法避免。

HashMap会引起这些问题,最主要的原因是对临界资源的处理没有加锁。

4、解决方案

 ConcurrentHashMap

JDK7采用分片加锁的方案,支持多线程操作一个Map容器

JDK8采用红黑树+数组+链表的结构,通过synchronized+CAS保证了线程安全,并实现了多线程协助扩容,加快扩容速度

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