最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

HashMap線程不安全問題解析

 更新時(shí)間:2023年11月02日 08:44:59   作者:Heloise_yangyuchang  
這篇文章主要介紹了HashMap線程不安全問題解析,HashMap的線程不安全體現(xiàn)在會(huì)造成死循環(huán)、數(shù)據(jù)丟失、數(shù)據(jù)覆蓋等問題,其中死循環(huán)和數(shù)據(jù)丟失是在JDK1.7中出現(xiàn)的問題,在JDK1.8中已經(jīng)得到解決,但是1.8中仍會(huì)有數(shù)據(jù)覆蓋這樣的問題,需要的朋友可以參考下

一、概述

結(jié)論:HashMap的線程不安全體現(xiàn)在會(huì)造成死循環(huán)、數(shù)據(jù)丟失、數(shù)據(jù)覆蓋等問題。其中死循環(huán)和數(shù)據(jù)丟失是在JDK1.7中出現(xiàn)的問題,在JDK1.8中已經(jīng)得到解決,但是1.8中仍會(huì)有數(shù)據(jù)覆蓋這樣的問題。HashMap是線程不安全的,線程安全場(chǎng)景應(yīng)該使用ConcurrentHashMap。

HashMap的線程不安全主要是發(fā)生在擴(kuò)容方法中,JDK1.7中HashMap的transfer函數(shù)如下:

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;
            }
        }
    } 

HashMap的擴(kuò)容操作(先擴(kuò)容在頭插法插入)會(huì)重新定位每個(gè)桶的下標(biāo),并采用頭插法將元素遷移到新數(shù)組中。頭插法會(huì)將鏈表的順序翻轉(zhuǎn),這也是造成死循環(huán)和數(shù)據(jù)丟失的關(guān)鍵。

二、JDK1.7中HashMap擴(kuò)容分析

比如現(xiàn)在有兩個(gè)線程A、B同時(shí)對(duì)下面這個(gè)HashMap進(jìn)行擴(kuò)容操作:

在這里插入圖片描述

正常擴(kuò)容后的結(jié)果如下(7%4=3%4=3):

在這里插入圖片描述

但是當(dāng)線程A執(zhí)行到上面transfer函數(shù)的第11行代碼時(shí),CPU時(shí)間片耗盡,線程A被掛起,即

 newTable[i] = e; //此處掛起,此時(shí)還沒有執(zhí)行

此時(shí)線程A中:e=3、next=7、e.next=null

在這里插入圖片描述

此時(shí)線程B開始執(zhí)行,并且線程B成功的完成了數(shù)據(jù)遷移,如下:

在這里插入圖片描述

這個(gè)是問題出現(xiàn)關(guān)鍵時(shí)間段,根據(jù)Java JMM,線程B執(zhí)行完數(shù)據(jù)遷移后,此時(shí)主內(nèi)存中newTable和table都是最新的,也就是說:7.next=3;3.next=null

此時(shí)線程A獲得CPU時(shí)間片繼續(xù)執(zhí)行newTable[i] = e,將3放入新數(shù)組對(duì)應(yīng)的位置,執(zhí)行完此輪循環(huán)后線程A的情況如下

在這里插入圖片描述

接著繼續(xù)執(zhí)行下一輪循環(huán),此時(shí)e=7,從主內(nèi)存中讀取e.next時(shí)發(fā)現(xiàn)主內(nèi)存中7.next=3,即next=3,并將7采用頭插法的方式放入新數(shù)組中,并繼續(xù)執(zhí)行完此輪循環(huán),結(jié)果如下:

在這里插入圖片描述

繼續(xù)執(zhí)行下一次循環(huán)可以發(fā)現(xiàn), e.next=null,所以此輪循環(huán)將會(huì)是最后一輪循環(huán)。接下來當(dāng)執(zhí)行完e.next=newTable[i]即3.next=7后,3和7之間就相互連接了,當(dāng)執(zhí)行完newTable[i]=e后,3被頭插法重新插入到鏈表中,執(zhí)行結(jié)果如下圖所示:

在這里插入圖片描述

到此線程A、B的擴(kuò)容操作完成。

顯然當(dāng)線程A執(zhí)行完后,HashMap中出現(xiàn)了環(huán)形結(jié)構(gòu),當(dāng)在以后對(duì)該HashMap進(jìn)行操作時(shí)會(huì)出現(xiàn)死循環(huán)。并且從上圖可以發(fā)現(xiàn),元素5在擴(kuò)容期間被莫名的丟失了,這就發(fā)生了數(shù)據(jù)丟失的問題。

三、JDK1.8中的線程不安全

JDK1.7出現(xiàn)的問題,在JDK1.8中已經(jīng)得到了很好的解決,JDK1.8直接在resize函數(shù)中完成了數(shù)據(jù)遷移。在進(jìn)行元素插入時(shí)使用的是尾插法然后在擴(kuò)容。

但是在1.8中仍會(huì)有數(shù)據(jù)覆蓋這樣的問題,先看put源碼:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null) //判斷是否出現(xiàn)hash碰撞,如果沒有hash碰撞則直接插入元素,此處線程不安全
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold) //++size此處線程不安全
            resize();
        afterNodeInsertion(evict);
        return null;
    }

其中代碼if ((p = tab[i = (n - 1) & hash]) == null) 是判斷是否出現(xiàn)hash碰撞: 比如兩個(gè)線程A、B都在進(jìn)行put操作,并且hash函數(shù)計(jì)算出的插入下標(biāo)是相同的,當(dāng)線程A執(zhí)行完第六行代碼后由于時(shí)間片耗盡導(dǎo)致被掛起,而線程B得到時(shí)間片后在該下標(biāo)處插入了元素,完成了正常的插入,然后線程A獲得時(shí)間片,由于之前已經(jīng)進(jìn)行了hash碰撞的判斷,所有此時(shí)不會(huì)再進(jìn)行判斷,而是直接進(jìn)行插入,這就導(dǎo)致了線程B插入的數(shù)據(jù)被線程A覆蓋了,從而線程不安全。

還有一種情況就是代碼 if (++size > threshold)中的++size: 同樣還是線程A、B,這兩個(gè)線程同時(shí)進(jìn)行put操作時(shí),假設(shè)當(dāng)前HashMap的zise大小為10,當(dāng)線程A執(zhí)行到此行代碼時(shí),從主內(nèi)存中獲得size的值為10后準(zhǔn)備進(jìn)行+1操作,但是由于時(shí)間片耗盡只好讓出CPU,線程B快樂的拿到CPU還是從主內(nèi)存中拿到size的值10進(jìn)行+1操作,完成了put操作并將size=11寫回主內(nèi)存,然后線程A再次拿到CPU并繼續(xù)執(zhí)行(此時(shí)size的值仍為10),當(dāng)執(zhí)行完put操作后,還是將size=11寫回內(nèi)存,此時(shí)線程A、B都執(zhí)行了一次put操作,但是size的值只增加了1,所有說還是由于數(shù)據(jù)覆蓋又導(dǎo)致了線程不安全。

四、總結(jié)

HashMap的線程不安全主要體現(xiàn)在兩個(gè)方面:

1.在JDK1.7中,當(dāng)并發(fā)執(zhí)行擴(kuò)容操作時(shí)會(huì)造成環(huán)形鏈和數(shù)據(jù)丟失的情況。

2.在JDK1.8中,在并發(fā)執(zhí)行put操作時(shí)會(huì)發(fā)生數(shù)據(jù)覆蓋的情況。

到此這篇關(guān)于HashMap線程不安全問題解析的文章就介紹到這了,更多相關(guān)HashMap線程不安全內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能

    詳解SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能

    本文主要介紹了SpringBoot項(xiàng)目整合Vue做一個(gè)完整的用戶注冊(cè)功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Spring中Transactional注解使用的心得(推薦)

    Spring中Transactional注解使用的心得(推薦)

    這篇文章主要介紹了Spring中Transactional注解使用的心得,事務(wù)是用來控制數(shù)據(jù)的ACID特性的,用于保證數(shù)據(jù)的正確性和完整性,需要的朋友可以參考下
    2022-10-10
  • Spring根據(jù)XML配置文件注入屬性的方法

    Spring根據(jù)XML配置文件注入屬性的方法

    下面小編就為大家?guī)硪黄猄pring根據(jù)XML配置文件注入屬性的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • jenkins通過docker部署后系統(tǒng)時(shí)間不正確問題的詳細(xì)解決方法

    jenkins通過docker部署后系統(tǒng)時(shí)間不正確問題的詳細(xì)解決方法

    這篇文章主要介紹了jenkins通過docker部署后系統(tǒng)時(shí)間不正確問題的詳細(xì)解決方法,Jenkins時(shí)間顯示慢8小時(shí)通常由JVM時(shí)區(qū)未正確配置引發(fā),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-06-06
  • Spring boot 集成 Druid 數(shù)據(jù)源過程詳解

    Spring boot 集成 Druid 數(shù)據(jù)源過程詳解

    這篇文章主要介紹了Spring boot 集成 Druid 數(shù)據(jù)源過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Spring的IOC原理詳情

    Spring的IOC原理詳情

    這篇文章主要介紹了Spring的IOC原理詳情,IOC是Inversion?of?Control的縮寫,多數(shù)書籍翻譯成“控制反轉(zhuǎn)”,還有些書籍翻譯成為控制反向或者控制倒置
    2022-07-07
  • SpringBoot使用validation-api實(shí)現(xiàn)對(duì)枚舉類參數(shù)校驗(yàn)的方法

    SpringBoot使用validation-api實(shí)現(xiàn)對(duì)枚舉類參數(shù)校驗(yàn)的方法

    這篇文章主要介紹了SpringBoot使用validation-api實(shí)現(xiàn)對(duì)枚舉類參數(shù)校驗(yàn),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Spring?Boot指定外部配置文件簡(jiǎn)單示例

    Spring?Boot指定外部配置文件簡(jiǎn)單示例

    Spring Boot可以讓你將配置外部化,這樣你就可以在不同的環(huán)境中使用相同的應(yīng)用程序代碼,這篇文章主要給大家介紹了關(guān)于Spring?Boot指定外部配置文件的相關(guān)資料,需要的朋友可以參考下
    2024-01-01
  • Vert.x Circuit Breaker使用及說明

    Vert.x Circuit Breaker使用及說明

    Vert.xCircuitBreaker是Vert.x框架中實(shí)現(xiàn)熔斷器模式的組件,通過配置最大失敗次數(shù)、超時(shí)時(shí)間等參數(shù),保護(hù)非阻塞和異步行為,提升系統(tǒng)韌性,文章詳細(xì)介紹了依賴配置、核心配置、使用方式、狀態(tài)管理、指標(biāo)監(jiān)控與擴(kuò)展使用等方面
    2026-01-01
  • win10和win7下java開發(fā)環(huán)境配置教程

    win10和win7下java開發(fā)環(huán)境配置教程

    這篇文章主要為大家詳細(xì)介紹了win7下Java開發(fā)環(huán)境配置教程,win10下Java開發(fā)環(huán)境配置,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-06-06

最新評(píng)論

扎兰屯市| 昌平区| 德江县| 称多县| 安康市| 务川| 武宁县| 小金县| 勐海县| 凤冈县| 崇仁县| 宜春市| 新民市| 伊金霍洛旗| 鄢陵县| 昌邑市| 永丰县| 揭东县| 芜湖市| 南靖县| 墨竹工卡县| 云林县| 湘阴县| 伊吾县| 枝江市| 温宿县| 景东| 三门峡市| 东丰县| 休宁县| 赣榆县| 万年县| 富锦市| 乌拉特中旗| 孟州市| 沿河| 贡觉县| 耿马| 天全县| 双桥区| 突泉县|