亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java?ConcurrentHashMap實現(xiàn)線程安全的代碼示例

 更新時間:2023年05月26日 09:45:41   作者:javacn  
眾所周知ConcurrentHashMap是HashMap的多線程版本,HashMap?在并發(fā)操作時會有各種問題,而這些問題,只要使用ConcurrentHashMap就可以完美解決了,本文將給詳細介紹ConcurrentHashMap是如何保證線程安全的

ConcurrentHashMap 線程安全實現(xiàn)簡述

ConcurrentHashMap 在 JDK 1.7 時,使用的是分段鎖也就是 Segment 來實現(xiàn)線程安全的。

然而它在 JDK 1.8 之后,使用的是 CAS + synchronized 或 CAS + volatile 來實現(xiàn)線程安全的。

JDK 1.7 底層結構

ConcurrentHashMap 在不同的 JDK 版本中實現(xiàn)是不同的,在 JDK 1.7 中它使用的是數(shù)組加鏈表的形式實現(xiàn)的,而數(shù)組又分為:大數(shù)組 Segment 和小數(shù)組 HashEntry。 大數(shù)組 Segment 可以理解為 MySQL 中的數(shù)據(jù)庫,而每個數(shù)據(jù)庫(Segment)中又有很多張表 HashEntry,每個 HashEntry 中又有多條數(shù)據(jù),這些數(shù)據(jù)是用鏈表連接的,如下圖所示:

JDK 1.7 線程安全實現(xiàn)

了解了 ConcurrentHashMap 的底層實現(xiàn),再看它的線程安全實現(xiàn)就比較簡單了。

接下來,我們通過添加元素 put 方法,來看 JDK 1.7 中 ConcurrentHashMap 是如何保證線程安全的,具體實現(xiàn)源碼如下:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 在往該 Segment 寫入前,先確保獲取到鎖
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); 
    V oldValue;
    try {
        // Segment 內(nèi)部數(shù)組
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                // 更新已有值...
            }
            else {
                // 放置 HashEntry 到特定位置,如果超過閾值則進行 rehash
                // 忽略其他代碼...
            }
        }
    } finally {
        // 釋放鎖
        unlock();
    }
    return oldValue;
}

從上述源碼我們可以看出,Segment 本身是基于 ReentrantLock 實現(xiàn)的加鎖和釋放鎖的操作,這樣就能保證多個線程同時訪問 ConcurrentHashMap 時,同一時間只有一個線程能操作相應的節(jié)點,這樣就保證了 ConcurrentHashMap 的線程安全了。

也就是說 ConcurrentHashMap 的線程安全是建立在 Segment 加鎖的基礎上的,所以我們把它稱之為分段鎖或片段鎖,如下圖所示:

JDK 1.8 底層結構

在 JDK 1.7 中,ConcurrentHashMap 雖然是線程安全的,但因為它的底層實現(xiàn)是數(shù)組 + 鏈表的形式,所以在數(shù)據(jù)比較多的情況下訪問是很慢的,因為要遍歷整個鏈表,而 JDK 1.8 則使用了數(shù)組 + 鏈表/紅黑樹的方式優(yōu)化了 ConcurrentHashMap 的實現(xiàn),具體實現(xiàn)結構如下:

鏈表升級為紅黑樹的規(guī)則:當鏈表長度大于 8,并且數(shù)組的長度大于 64 時,鏈表就會升級為紅黑樹的結構。

PS:ConcurrentHashMap 在 JDK 1.8 雖然保留了 Segment 的定義,但這僅僅是為了保證序列化時的兼容性,不再有任何結構上的用處了。

JDK 1.8 線程安全實現(xiàn)

在 JDK 1.8 中 ConcurrentHashMap 使用的是 CAS + volatile 或 synchronized 的方式來保證線程安全的,它的核心實現(xiàn)源碼如下:

final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh; K fk; V fv;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 節(jié)點為空
            // 利用 CAS 去進行無鎖線程安全操作,如果 bin 是空的
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                break; 
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else if (onlyIfAbsent
                 && fh == hash
                 && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                 && (fv = f.val) != null)
            return fv;
        else {
            V oldVal = null;
            synchronized (f) {
                   // 細粒度的同步修改操作... 
                }
            }
            // 如果超過閾值,升級為紅黑樹
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

從上述源碼可以看出,在 JDK 1.8 中,添加元素時首先會判斷容器是否為空,如果為空則使用 volatile 加 CAS 來初始化。如果容器不為空則根據(jù)存儲的元素計算該位置是否為空,如果為空則利用 CAS 設置該節(jié)點;如果不為空則使用 synchronize 加鎖,遍歷桶中的數(shù)據(jù),替換或新增節(jié)點到桶中,最后再判斷是否需要轉(zhuǎn)為紅黑樹,這樣就能保證并發(fā)訪問時的線程安全了。

我們把上述流程簡化一下,我們可以簡單的認為在 JDK 1.8 中,ConcurrentHashMap 是在頭節(jié)點加鎖來保證線程安全的,鎖的粒度相比 Segment 來說更小了,發(fā)生沖突和加鎖的頻率降低了,并發(fā)操作的性能就提高了。而且 JDK 1.8 使用的是紅黑樹優(yōu)化了之前的固定鏈表,那么當數(shù)據(jù)量比較大的時候,查詢性能也得到了很大的提升,從之前的 O(n) 優(yōu)化到了 O(logn) 的時間復雜度,具體加鎖示意圖如下:

小結

ConcurrentHashMap 在 JDK 1.7 時使用的是數(shù)據(jù)加鏈表的形式實現(xiàn)的,其中數(shù)組分為兩類:大數(shù)組 Segment 和小數(shù)組 HashEntry,而加鎖是通過給 Segment 添加 ReentrantLock 鎖來實現(xiàn)線程安全的。而 JDK 1.8 中 ConcurrentHashMap 使用的是數(shù)組+鏈表/紅黑樹的方式實現(xiàn)的,它是通過 CAS 或 synchronized 來實現(xiàn)線程安全的,并且它的鎖粒度更小,查詢性能也更高。

以上就是Java ConcurrentHashMap實現(xiàn)線程安全的代碼示例的詳細內(nèi)容,更多關于Java ConcurrentHashMap線程安全的資料請關注腳本之家其它相關文章!

相關文章

  • java實現(xiàn)導出Excel的功能

    java實現(xiàn)導出Excel的功能

    這篇文章主要為大家詳細介紹了java實現(xiàn)導出Excel的功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-05-05
  • SpringBoot中MapStruct實現(xiàn)優(yōu)雅的數(shù)據(jù)復制

    SpringBoot中MapStruct實現(xiàn)優(yōu)雅的數(shù)據(jù)復制

    本文主要介紹了SpringBoot中MapStruct實現(xiàn)優(yōu)雅的數(shù)據(jù)復制,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-08-08
  • java組件commons-fileupload實現(xiàn)文件上傳

    java組件commons-fileupload實現(xiàn)文件上傳

    這篇文章主要介紹了java借助commons-fileupload組件實現(xiàn)文件上傳,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Java對象創(chuàng)建內(nèi)存案例解析

    Java對象創(chuàng)建內(nèi)存案例解析

    這篇文章主要介紹了Java對象創(chuàng)建內(nèi)存案例解析,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • 詳解Mybatis是如何解析配置文件的

    詳解Mybatis是如何解析配置文件的

    這篇文章主要介紹了詳解Mybatis是如何解析配置文件的,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • Java持久層面試題目及答案整理

    Java持久層面試題目及答案整理

    在本篇文章里小編給大家分享的是一篇關于Java持久層面試題目及答案整理內(nèi)容,需要的朋友們學習參考下。
    2020-02-02
  • java編碼IDEA主題推薦

    java編碼IDEA主題推薦

    在這篇文章中,我精選了幾個比較是和?Java?編碼的?IDEA?主題供小伙伴們選擇。另外,我自己用的是?One?Dark?theme?這款,有需要的朋友可以借鑒參考下,希望大家喜歡
    2022-01-01
  • springboot+feign+Hystrix整合(親測有效)

    springboot+feign+Hystrix整合(親測有效)

    本文主要介紹了springboot+feign+Hystrix整合,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-11-11
  • Java多線程atomic包介紹及使用方法

    Java多線程atomic包介紹及使用方法

    這篇文章主要介紹了Java多線程atomic包介紹及使用方法,涉及原子更新基本類型介紹及代碼示例,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • java中File類應用遍歷文件夾下所有文件

    java中File類應用遍歷文件夾下所有文件

    這篇文章主要為大家詳細介紹了java中File類應用遍歷文件夾下所有文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-08-08

最新評論