欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

HashMap線程不安全問題解析

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

一、概述

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

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

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的擴容操作(先擴容在頭插法插入)會重新定位每個桶的下標,并采用頭插法將元素遷移到新數組中。頭插法會將鏈表的順序翻轉,這也是造成死循環(huán)和數據丟失的關鍵。

二、JDK1.7中HashMap擴容分析

比如現在有兩個線程A、B同時對下面這個HashMap進行擴容操作:

在這里插入圖片描述

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

在這里插入圖片描述

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

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

此時線程A中:e=3、next=7、e.next=null

在這里插入圖片描述

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

在這里插入圖片描述

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

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

在這里插入圖片描述

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

在這里插入圖片描述

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

在這里插入圖片描述

到此線程A、B的擴容操作完成。

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

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

JDK1.7出現的問題,在JDK1.8中已經得到了很好的解決,JDK1.8直接在resize函數中完成了數據遷移。在進行元素插入時使用的是尾插法然后在擴容。

但是在1.8中仍會有數據覆蓋這樣的問題,先看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) //判斷是否出現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) 是判斷是否出現hash碰撞: 比如兩個線程A、B都在進行put操作,并且hash函數計算出的插入下標是相同的,當線程A執(zhí)行完第六行代碼后由于時間片耗盡導致被掛起,而線程B得到時間片后在該下標處插入了元素,完成了正常的插入,然后線程A獲得時間片,由于之前已經進行了hash碰撞的判斷,所有此時不會再進行判斷,而是直接進行插入,這就導致了線程B插入的數據被線程A覆蓋了,從而線程不安全。

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

四、總結

HashMap的線程不安全主要體現在兩個方面:

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

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

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

相關文章

  • 用Java打造簡易計算器的實現步驟

    用Java打造簡易計算器的實現步驟

    這篇文章主要介紹了如何設計和實現一個簡單的Java命令行計算器程序,該程序能夠執(zhí)行基本的數學運算(加、減、乘、除),文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2025-01-01
  • Spring?Boot?整合JPA?數據模型關聯(lián)使用操作(一對一、一對多、多對多)

    Spring?Boot?整合JPA?數據模型關聯(lián)使用操作(一對一、一對多、多對多)

    這篇文章主要介紹了Spring?Boot?整合JPA?數據模型關聯(lián)操作(一對一、一對多、多對多),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • java并發(fā)編程_線程池的使用方法(詳解)

    java并發(fā)編程_線程池的使用方法(詳解)

    下面小編就為大家?guī)硪黄猨ava并發(fā)編程_線程池的使用方法(詳解)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • mongo分布式鎖Java實現方法(推薦)

    mongo分布式鎖Java實現方法(推薦)

    下面小編就為大家?guī)硪黄猰ongo分布式鎖Java實現方法(推薦)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • Java多線程中的Phaser使用解析

    Java多線程中的Phaser使用解析

    這篇文章主要介紹了Java多線程中的Phaser使用解析,java多線程技術提供了Phaser工具類,Phaser表示“階段器”,用來解決控制多個線程分階段共同完成任務的情景問題,其作用相比CountDownLatch和CyclicBarrier更加靈活,需要的朋友可以參考下
    2023-11-11
  • 淺談Java中Lambda表達式的相關操作

    淺談Java中Lambda表達式的相關操作

    java8新特性,Lambda是一個匿名函數,類似Python中的Lambda表達式、js中的箭頭函數,目的簡化操作,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • 在java中 利用匿名內部類進行較簡潔的雙括弧初始化的方法

    在java中 利用匿名內部類進行較簡潔的雙括弧初始化的方法

    本篇文章小編將為大家介紹,關于在java中 利用匿名內部類進行較簡潔的雙括弧初始化的方法,有需要的朋友可以參考一下
    2013-04-04
  • 全面了解Java中的CAS機制

    全面了解Java中的CAS機制

    下面小編就為大家?guī)硪黄媪私釰ava中的CAS機制。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • springboot整合redis之消息隊列

    springboot整合redis之消息隊列

    本文主要介紹了springboot整合redis之消息隊列,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-06-06
  • Spring在代碼中獲取bean的幾種方式詳解

    Spring在代碼中獲取bean的幾種方式詳解

    這篇文章主要介紹了Spring在代碼中獲取bean的幾種方式詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08

最新評論