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

Java中的ConcurrentHashMap原理詳解

 更新時間:2023年12月27日 09:45:07   作者:笑我歸無處  
這篇文章主要介紹了Java中的ConcurrentHashMap原理詳解,ConcurrentHashMap和HashMap一樣,是一個存放鍵值對的容器,使用hash算法來獲取值的地址,因此時間復(fù)雜度是O(1),查詢非???需要的朋友可以參考下

一、什么是ConcurrentHashMap

ConcurrentHashMap和HashMap一樣,是一個存放鍵值對的容器。

使用hash算法來獲取值的地址,因此時間復(fù)雜度是O(1)。查詢非???。 同時,ConcurrentHashMap是線程安全的HashMap。專門用于多線程環(huán)境。

二、ConcurrentHashMap和HashMap以及Hashtable的區(qū)別

2.1 HashMap

HashMap是線程不安全的,因為HashMap中操作都沒有加鎖,因此在多線程環(huán)境下會導(dǎo)致數(shù)據(jù)覆蓋之類的問題,所以,在多線程中使用HashMap是會拋出異常的。

2.2 HashTable

HashTable是線程安全的,但是HashTable只是單純的在put()方法上加上synchronized。保證插入時阻塞其他線程的插入操作。雖然安全,但因為設(shè)計簡單,所以性能低下。

2.3 ConcurrentHashMap

ConcurrentHashMap是線程安全的,ConcurrentHashMap并非鎖住整個方法,而是通過原子操作和局部加鎖的方法保證了多線程的線程安全,且盡可能減少了性能損耗。

由此可見,HashTable可真是一無是處…

三、ConcurrentHashMap原理

這一節(jié)專門介紹ConcurrentHashMap是如何保證線程安全的。如果想詳細(xì)了解ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu),請參考HashMap。

3.1 volatile修飾的節(jié)點數(shù)組

請看源碼

//ConcurrentHashMap使用volatile修飾節(jié)點數(shù)組,保證其可見性,禁止指令重排。
transient volatile Node<K,V>[] table;

再看看HashMap是怎么做的

//HashMap沒有用volatile修飾節(jié)點數(shù)組。
transient Node<K,V>[] table;

顯然,HashMap并不是為多線程環(huán)境設(shè)計的。

3.2 ConcurrentHashMap的put()方法

//put()方法直接調(diào)用putVal()方法
public V put(K key, V value) {
 return putVal(key, value, false);
}
//所以直接看putVal()方法。
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;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

我來給大家講解一下步驟把。

public V put(K key, V value) {

首先,put()方法是沒有用synchronized修飾的。

for (Node<K,V>[] tab = table;;)

新插入一個節(jié)點時,首先會進(jìn)入一個死循環(huán), 情商高的就會說,這是一個樂觀鎖 進(jìn)入樂觀鎖后,

if (tab == null || (n = tab.length) == 0)
    tab = initTable();

如果tab未被初始化,則先將tab初始化。此時,這輪循環(huán)結(jié)束,因為被樂觀鎖鎖住,開始下一輪循環(huán)。 第二輪循環(huán),此時tab已經(jīng)被初始化了,所以跳過。

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null,
                 new Node<K,V>(hash, key, value, null)))
        break;                   // no lock when adding to empty bin
}

接下來通過key的hash值來判斷table中是否存在相同的key,如果不存在,執(zhí)行casTabAt()方法。 注意,這個操作時不加鎖的,看到里面的那行注釋了么// no lock when adding to empty bin。位置為空時不加鎖。 這里其實是利用了一個CAS操作。

CAS(Compare-And-Swap):比較并交換

這里就插播一個小知識,CAS就是通過一個原子操作,用預(yù)期值去和實際值做對比,如果實際值和預(yù)期相同,則做更新操作。 如果預(yù)期值和實際不同,我們就認(rèn)為,其他線程更新了這個值,此時不做更新操作。 而且這整個流程是原子性的,所以只要實際值和預(yù)期值相同,就能保證這次更新不會被其他線程影響。

好了,我們繼續(xù)。 既然這里用了CAS操作去更新值,那么就存在兩者情況。

  • 實際值和預(yù)期值相同 相同時,直接將值插入,因為此時是線程安全的。好了,這時插入操作完成。使用break;跳出了樂觀鎖。循環(huán)結(jié)束。
  • 實際值和預(yù)期值不同 不同時,不進(jìn)行操作,因為此時這個值已經(jīng)被其他線程修改過了,此時這輪操作就結(jié)束了,因為還被樂觀鎖鎖住,進(jìn)入第三輪循環(huán)。

第三輪循環(huán)中,前面的判斷又會重新執(zhí)行一次,我就跳過不說了,進(jìn)入后面的判斷。

 else if ((fh = f.hash) == MOVED)
    tab = helpTransfer(tab, f);

這里判斷的是tab的狀態(tài),MOVED表示在擴(kuò)容中,如果在擴(kuò)容中,幫助其擴(kuò)容。幫助完了后就會進(jìn)行第四輪循環(huán)。 終于,來到了最后一輪循環(huán)。

else {
    V oldVal = null;
    synchronized (f) {
        if (tabAt(tab, i) == f) {
            if (fh >= 0) {
                binCount = 1;
                for (Node<K,V> e = f;; ++binCount) {
                    K ek;
                    if (e.hash == hash &&
                        ((ek = e.key) == key ||
                         (ek != null && key.equals(ek)))) {
                        oldVal = e.val;
                        if (!onlyIfAbsent)
                            e.val = value;
                        break;
                    }
                    Node<K,V> pred = e;
                    if ((e = e.next) == null) {
                        pred.next = new Node<K,V>(hash, key,
                                                  value, null);
                        break;
                    }
                }
            }
            else if (f instanceof TreeBin) {
                Node<K,V> p;
                binCount = 2;
                if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                               value)) != null) {
                    oldVal = p.val;
                    if (!onlyIfAbsent)
                        p.val = value;
                }
            }
        }
    }
    if (binCount != 0) {
        if (binCount >= TREEIFY_THRESHOLD)
            treeifyBin(tab, i);
        if (oldVal != null)
            return oldVal;
        break;
    }
}

上面的判斷都不滿足時,就會進(jìn)入最后的分支,這條分支表示,key的hash值位置不為null(之前的判斷是hash值為null時直接做插入操作),表示發(fā)生了hash沖突,此時節(jié)點就要通過鏈表的形式存儲這個插入的新值。Node類是有next字段的,用來指向鏈表的下一個位置,新節(jié)點就往這插。

synchronized (f) {

看,終于加排它鎖了,只有在發(fā)生hash沖突的時候才加了排它鎖。

if (tabAt(tab, i) == f) {
	if (fh >= 0) {

重新判斷當(dāng)前節(jié)點是不是第二輪判斷過的節(jié)點,如果不是,表示節(jié)點被其他線程改過了,進(jìn)入下一輪循環(huán), 如果是,再次判斷是否在擴(kuò)容中,如果是,進(jìn)入下一輪循環(huán), 如果不是,其他線程沒改過,繼續(xù)走,

for (Node<K,V> e = f;; ++binCount) {

for循環(huán),循環(huán)遍歷這個節(jié)點上的鏈表,

if (e.hash == hash &&
    ((ek = e.key) == key ||
     (ek != null && key.equals(ek)))) {
    oldVal = e.val;
    if (!onlyIfAbsent)
        e.val = value;
    break;
}

找到一個hash值相同,且key也完全相同的節(jié)點,更新這個節(jié)點。 如果找不到

if ((e = e.next) == null) {
	pred.next = new Node<K,V>(hash, key,
                              value, null);
    break;
}

往鏈表最后插入這個新節(jié)點。因為在排他鎖中,這些操作都可以直接操作。終于到這插入就基本完成了。

總結(jié)

做插入操作時,首先進(jìn)入樂觀鎖, 然后,在樂觀鎖中判斷容器是否初始化, 如果沒初始化則初始化容器, 如果已經(jīng)初始化,則判斷該hash位置的節(jié)點是否為空,如果為空,則通過CAS操作進(jìn)行插入。 如果該節(jié)點不為空,再判斷容器是否在擴(kuò)容中,如果在擴(kuò)容,則幫助其擴(kuò)容。 如果沒有擴(kuò)容,則進(jìn)行最后一步,先加鎖,然后找到hash值相同的那個節(jié)點(hash沖突), 循環(huán)判斷這個節(jié)點上的鏈表,決定做覆蓋操作還是插入操作。 循環(huán)結(jié)束,插入完畢。

3.3 ConcurrentHashMap的get()方法

//ConcurrentHashMap的get()方法是不加鎖的,方法內(nèi)部也沒加鎖。
public V get(Object key)

看上面這代碼,ConcurrentHashMap的get()方法是不加鎖的,為什么可以不加鎖?因為table有volatile關(guān)鍵字修飾,保證每次獲取值都是最新的。

//Hashtable的get()是加鎖的,所以性能差。
public synchronized V get(Object key) 

再看看Hashtable,差距啊。

四、使用場景

嗯,多線程環(huán)境下,更新少,查詢多時使用的話,性能比較高。

樂觀鎖嘛,認(rèn)為更新操作時不會被其他線程影響。

所以時候再更新少的情況下性能高。

到此這篇關(guān)于Java中的ConcurrentHashMap原理詳解的文章就介紹到這了,更多相關(guān)ConcurrentHashMap原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • IDEA 非常重要的一些設(shè)置項(一連串的問題差點讓我重新用回 Eclipse)

    IDEA 非常重要的一些設(shè)置項(一連串的問題差點讓我重新用回 Eclipse)

    這篇文章主要介紹了IDEA 非常重要的一些設(shè)置項(一連串的問題差點讓我重新用回 Eclipse),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-08-08
  • spring aop底層源碼執(zhí)行邏輯剖析(源碼解析)

    spring aop底層源碼執(zhí)行邏輯剖析(源碼解析)

    這篇文章主要介紹了spring aop底層源碼執(zhí)行邏輯剖析,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-08-08
  • 歸并算法之有序數(shù)組合并算法實現(xiàn)

    歸并算法之有序數(shù)組合并算法實現(xiàn)

    這篇文章主要介紹了歸并算法之有序數(shù)組合并算法實現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • Java容器源碼LinkedList原理解析

    Java容器源碼LinkedList原理解析

    這篇文章主要介紹了Java容器源碼LinkedList原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-11-11
  • springboot項目如何使用切面記錄用戶操作日志

    springboot項目如何使用切面記錄用戶操作日志

    這篇文章主要介紹了springboot項目如何使用切面記錄用戶操作日志,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • Spring Boot開發(fā)Web應(yīng)用詳解

    Spring Boot開發(fā)Web應(yīng)用詳解

    這篇文章主要介紹了Spring Boot開發(fā)Web應(yīng)用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • SpringBoot實用小技巧之如何動態(tài)設(shè)置日志級別

    SpringBoot實用小技巧之如何動態(tài)設(shè)置日志級別

    這篇文章主要給大家介紹了關(guān)于SpringBoot實用小技巧之如何動態(tài)設(shè)置日志級別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Spring實現(xiàn)跨域的幾種方式小結(jié)

    Spring實現(xiàn)跨域的幾種方式小結(jié)

    這篇文章主要給大家總結(jié)了幾種Spring實現(xiàn)跨域的方式,文中通過代碼示例介紹的非常詳細(xì),對我們的學(xué)習(xí)活工作有一定的幫助,需要的朋友可以參考下
    2023-07-07
  • Mybatis中collection和association的使用區(qū)別詳解

    Mybatis中collection和association的使用區(qū)別詳解

    這篇文章主要介紹了Mybatis中collection和association的使用區(qū)別詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • Java使用橋接模式實現(xiàn)開關(guān)和電燈照明功能詳解

    Java使用橋接模式實現(xiàn)開關(guān)和電燈照明功能詳解

    這篇文章主要介紹了Java使用橋接模式實現(xiàn)開關(guān)和電燈照明功能,較為詳細(xì)的講述了橋接模式的概念、原理并結(jié)合實例形式分析了Java使用橋接模式實現(xiàn)開關(guān)和電燈照明功能相關(guān)操作步驟與注意事項,需要的朋友可以參考下
    2018-05-05

最新評論