1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        面試再被問到 ConcurrentHashMap,把這篇文章甩給他!

        共 12311字,需瀏覽 25分鐘

         ·

        2020-10-29 23:06

        本文來源:

        https://www.cnblogs.com/ITtangtang/p/3948786.html


        一、背景:

        線程不安全的HashMap

        因為多線程環(huán)境下,使用Hashmap進行put操作會引起死循環(huán),導(dǎo)致CPU利用率接近100%,所以在并發(fā)情況下不能使用HashMap。

        效率低下的HashTable容器

        HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下。

        因為當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態(tài)。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,并且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。

        鎖分段技術(shù)

        HashTable容器在競爭激烈的并發(fā)環(huán)境下表現(xiàn)出效率低下的原因,是因為所有訪問HashTable的線程都必須競爭同一把鎖

        那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數(shù)據(jù),那么當多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖競爭,從而可以有效的提高并發(fā)訪問效率

        這就是ConcurrentHashMap所使用的鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲,然后給每一段數(shù)據(jù)配一把鎖,當一個線程占用鎖訪問其中一個段數(shù)據(jù)的時候,其他段的數(shù)據(jù)也能被其他線程訪問。

        有些方法需要跨段,比如size()和containsValue(),它們可能需要鎖定整個表而而不僅僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖。

        這里“按順序”是很重要的,否則極有可能出現(xiàn)死鎖,在ConcurrentHashMap內(nèi)部,段數(shù)組是final的,并且其成員變量實際上也是final的,但是,僅僅是將數(shù)組聲明為final的并不保證數(shù)組成員也是final的,這需要實現(xiàn)上的保證。這可以確保不會出現(xiàn)死鎖,因為獲得鎖的順序是固定的。

        ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲鍵值對數(shù)據(jù)。

        一個ConcurrentHashMap里包含一個Segment數(shù)組,Segment的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu), 一個Segment里包含一個HashEntry數(shù)組,每個HashEntry是一個鏈表結(jié)構(gòu)的元素, 每個Segment守護者一個HashEntry數(shù)組里的元素,當對HashEntry數(shù)組的數(shù)據(jù)進行修改時,必須首先獲得它對應(yīng)的Segment鎖。

        二、應(yīng)用場景

        當有一個大數(shù)組時需要在多個線程共享時就可以考慮是否把它給分層多個節(jié)點了,避免大鎖。并可以考慮通過hash算法進行一些模塊定位。

        其實不止用于線程,當設(shè)計數(shù)據(jù)表的事務(wù)時(事務(wù)某種意義上也是同步機制的體現(xiàn)),可以把一個表看成一個需要同步的數(shù)組,如果操作的表數(shù)據(jù)太多時就可以考慮事務(wù)分離了(這也是為什么要避免大表的出現(xiàn)),比如把數(shù)據(jù)進行字段拆分,水平分表等.

        三、源碼解讀

        ConcurrentHashMap(1.7及之前)中主要實體類就是三個:ConcurrentHashMap(整個Hash表),Segment(桶),HashEntry(節(jié)點)

        對應(yīng)上面的圖可以看出之間的關(guān)系

        /**?
        *?The?segments,?each?of?which?is?a?specialized?hash?table?
        */
        ??
        final?Segment[]?segments;

        不變(Immutable)和易變(Volatile)

        ConcurrentHashMap完全允許多個讀操作并發(fā)進行,讀操作并不需要加鎖。如果使用傳統(tǒng)的技術(shù),如HashMap中的實現(xiàn),如果允許可以在hash鏈的中間添加或刪除元素,讀操作不加鎖將得到不一致的數(shù)據(jù)。

        ConcurrentHashMap實現(xiàn)技術(shù)是保證HashEntry幾乎是不可變的。HashEntry代表每個hash鏈中的一個節(jié)點,其結(jié)構(gòu)如下所示:

        ?static?final?class?HashEntry<K,V>?{??
        ?????final?K?key;??
        ?????final?int?hash;??
        ?????volatile?V?value;??
        ?????final?HashEntry?next;??
        ?}?

        可以看到除了value不是final的,其它值都是final的,這意味著不能從hash鏈的中間或尾部添加或刪除節(jié)點,因為這需要修改next 引用值,所有的節(jié)點的修改只能從頭部開始。對于put操作,可以一律添加到Hash鏈的頭部。

        但是對于remove操作,可能需要從中間刪除一個節(jié)點,這就需要將要刪除節(jié)點的前面所有節(jié)點整個復(fù)制一遍,最后一個節(jié)點指向要刪除結(jié)點的下一個結(jié)點。這在講解刪除操作時還會詳述。為了確保讀操作能夠看到最新的值,將value設(shè)置成volatile,這避免了加鎖。

        其它

        為了加快定位段以及段中hash槽的速度,每個段hash槽的的個數(shù)都是2^n,這使得通過位運算就可以定位段和段中hash槽的位置。當并發(fā)級別為默認值16時,也就是段的個數(shù),hash值的高4位決定分配在哪個段中。

        但是我們也不要忘記《算法導(dǎo)論》給我們的教訓:hash槽的的個數(shù)不應(yīng)該是 2^n,這可能導(dǎo)致hash槽分配不均,這需要對hash值重新再hash一次。(這段似乎有點多余了 )

        定位操作:

        final?Segment?segmentFor(int?hash)?{??
        ?????return?segments[(hash?>>>?segmentShift)?&?segmentMask];??
        ?}

        既然ConcurrentHashMap使用分段鎖Segment來保護不同段的數(shù)據(jù),那么在插入和獲取元素的時候,必須先通過哈希算法定位到Segment??梢钥吹紺oncurrentHashMap會首先使用Wang/Jenkins hash的變種算法對元素的hashCode進行一次再哈希。

        再哈希,其目的是為了減少哈希沖突,使元素能夠均勻的分布在不同的Segment上,從而提高容器的存取效率。假如哈希的質(zhì)量差到極點,那么所有的元素都在一個Segment中,不僅存取元素緩慢,分段鎖也會失去意義。我做了一個測試,不通過再哈希而直接執(zhí)行哈希計算。

        System.out.println(Integer.parseInt("0001111",?2)?&?15);
        System.out.println(Integer.parseInt("0011111",?2)?&?15);
        System.out.println(Integer.parseInt("0111111",?2)?&?15);
        System.out.println(Integer.parseInt("1111111",?2)?&?15);

        計算后輸出的哈希值全是15,通過這個例子可以發(fā)現(xiàn)如果不進行再哈希,哈希沖突會非常嚴重,因為只要低位一樣,無論高位是什么數(shù),其哈希值總是一樣。我們再把上面的二進制數(shù)據(jù)進行再哈希后結(jié)果如下,為了方便閱讀,不足32位的高位補了0,每隔四位用豎線分割下。

        0100|0111|0110|0111|1101|1010|0100|1110
        1111|0111|0100|0011|0000|0001|1011|1000
        0111|0111|0110|1001|0100|0110|0011|1110
        1000|0011|0000|0000|1100|1000|0001|1010

        可以發(fā)現(xiàn)每一位的數(shù)據(jù)都散列開了,通過這種再哈希能讓數(shù)字的每一位都能參加到哈希運算當中,從而減少哈希沖突。ConcurrentHashMap通過以下哈希算法定位segment。

        默認情況下segmentShift為28,segmentMask為15,再哈希后的數(shù)最大是32位二進制數(shù)據(jù),向右無符號移動28位,意思是讓高4位參與到hash運算中, (hash >>> segmentShift) & segmentMask的運算結(jié)果分別是4,15,7和8,可以看到hash值沒有發(fā)生沖突。

        final?Segment?segmentFor(int?hash)?{
        ????return?segments[(hash?>>>?segmentShift)?&?segmentMask];
        }

        數(shù)據(jù)結(jié)構(gòu)

        所有的成員都是final的,其中segmentMask和segmentShift主要是為了定位段,參見上面的segmentFor方法。

        關(guān)于Hash表的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),這里不想做過多的探討。Hash表的一個很重要方面就是如何解決hash沖突,ConcurrentHashMap 和HashMap使用相同的方式,都是將hash值相同的節(jié)點放在一個hash鏈中。與HashMap不同的是,ConcurrentHashMap使用多個子Hash表,也就是段(Segment)。

        每個Segment相當于一個子Hash表,它的數(shù)據(jù)成員如下:

        static?final?class?Segment<K,V>?extends?ReentrantLock?implements?Serializable?{????
        ?????????/**?
        ??????????*?The?number?of?elements?in?this?segment's?region.?
        ??????????*/

        ?????????transient?volatileint?count;??
        ?????????/**?
        ??????????*?Number?of?updates?that?alter?the?size?of?the?table.?This?is?
        ??????????*?used?during?bulk-read?methods?to?make?sure?they?see?a?
        ??????????*?consistent?snapshot:?If?modCounts?change?during?a?traversal?
        ??????????*?of?segments?computing?size?or?checking?containsValue,?then?
        ??????????*?we?might?have?an?inconsistent?view?of?state?so?(usually)?
        ??????????*?must?retry.?
        ??????????*/

        ?????????transient?int?modCount;??
        ?????????/**?
        ??????????*?The?table?is?rehashed?when?its?size?exceeds?this?threshold.?
        ??????????*?(The?value?of?this?field?is?always?(int)(capacity?*?
        ??????????*?loadFactor)
        .)?
        ??????????*/

        ?????????transient?int?threshold;??
        ?????????/**?
        ??????????*?The?per-segment?table.?
        ??????????*/

        ?????????transient?volatile?HashEntry[]?table;??
        ?????????/**?
        ??????????*?The?load?factor?for?the?hash?table.??Even?though?this?value?
        ??????????*?is?same?for?all?segments,?it?is?replicated?to?avoid?needing?
        ??????????*?links?to?outer?object.?
        ??????????*?@serial?
        ??????????*/

        ?????????final?float?loadFactor;??
        ?}

        count用來統(tǒng)計該段數(shù)據(jù)的個數(shù),它是volatile,它用來協(xié)調(diào)修改和讀取操作,以保證讀取操作能夠讀取到幾乎最新的修改。

        協(xié)調(diào)方式是這樣的,每次修改操作做了結(jié)構(gòu)上的改變,如增加/刪除節(jié)點(修改節(jié)點的值不算結(jié)構(gòu)上的改變),都要寫count值,每次讀取操作開始都要讀取count的值。這利用了 Java 5中對volatile語義的增強,對同一個volatile變量的寫和讀存在happens-before關(guān)系。

        modCount統(tǒng)計段結(jié)構(gòu)改變的次數(shù),主要是為了檢測對多個段進行遍歷過程中某個段是否發(fā)生改變,在講述跨段操作時會還會詳述。

        threashold用來表示需要進行rehash的界限值。

        table數(shù)組存儲段中節(jié)點,每個數(shù)組元素是個hash鏈,用HashEntry表示。table也是volatile,這使得能夠讀取到最新的 table值而不需要同步。loadFactor表示負載因子。

        刪除操作remove(key)

        public?V?remove(Object?key)?{??
        ???hash?=?hash(key.hashCode());???
        ???return?segmentFor(hash).remove(key,?hash,?null);???
        }

        整個操作是先定位到段,然后委托給段的remove操作。當多個刪除操作并發(fā)進行時,只要它們所在的段不相同,它們就可以同時進行。

        下面是Segment的remove方法實現(xiàn):

        V?remove(Object?key,?int?hash,?Object?value)?{??
        ?????lock();??
        ?????try?{??
        ?????????int?c?=?count?-?1;??
        ?????????HashEntry[]?tab?=?table;??
        ?????????int?index?=?hash?&?(tab.length?-?1);??
        ?????????HashEntry?first?=?tab[index];??
        ?????????HashEntry?e?=?first;??
        ?????????while?(e?!=?null?&&?(e.hash?!=?hash?||?!key.equals(e.key)))??
        ?????????????e?=?e.next;??
        ?????????V?oldValue?=?null;??
        ?????????if?(e?!=?null)?{??
        ?????????????V?v?=?e.value;??
        ?????????????if?(value?==?null?||?value.equals(v))?{??
        ?????????????????oldValue?=?v;??

        ?????????????????//?All?entries?following?removed?node?can?stay??
        ?????????????????//?in?list,?but?all?preceding?ones?need?to?be??
        ?????????????????//?cloned.??
        ?????????????????++modCount;??
        ?????????????????HashEntry?newFirst?=?e.next;??
        ?????????????????*for?(HashEntry?p?=?first;?p?!=?e;?p?=?p.next)??
        ?????????????????????*newFirst?=?new?HashEntry(p.key,?p.hash,??
        ???????????????????????????????????????????????????newFirst,?p.value);??
        ?????????????????tab[index]?=?newFirst;??
        ?????????????????count?=?c;?//?write-volatile??
        ?????????????}??
        ?????????}??
        ?????????return?oldValue;??
        ?????}?finally?{??
        ?????????unlock();??
        ?????}??
        ?}

        整個操作是在持有段鎖的情況下執(zhí)行的,空白行之前的行主要是定位到要刪除的節(jié)點e。接下來,如果不存在這個節(jié)點就直接返回null,否則就要將e前面的結(jié)點復(fù)制一遍,尾結(jié)點指向e的下一個結(jié)點。e后面的結(jié)點不需要復(fù)制,它們可以重用。

        中間那個for循環(huán)是做什么用的呢?(*號標記)從代碼來看,就是將定位之后的所有entry克隆并拼回前面去,但有必要嗎?每次刪除一個元素就要將那之前的元素克隆一遍?

        這點其實是由entry的不變性來決定的,仔細觀察entry定義,發(fā)現(xiàn)除了value,其他所有屬性都是用final來修飾的,這意味著在第一次設(shè)置了next域之后便不能再改變它,取而代之的是將它之前的節(jié)點全都克隆一次。

        至于entry為什么要設(shè)置為不變性,這跟不變性的訪問不需要同步從而節(jié)省時間有關(guān)

        下面是個示意圖

        第二個圖其實有點問題,復(fù)制的結(jié)點中應(yīng)該是值為2的結(jié)點在前面,值為1的結(jié)點在后面,也就是剛好和原來結(jié)點順序相反,還好這不影響我們的討論。

        整個remove實現(xiàn)并不復(fù)雜,但是需要注意如下幾點。

        • 第一,當要刪除的結(jié)點存在時,刪除的最后一步操作要將count的值減一。這必須是最后一步操作,否則讀取操作可能看不到之前對段所做的結(jié)構(gòu)性修改。

        • 第二,remove執(zhí)行的開始就將table賦給一個局部變量tab,這是因為table是 volatile變量,讀寫volatile變量的開銷很大。編譯器也不能對volatile變量的讀寫做任何優(yōu)化,直接多次訪問非volatile實例變量沒有多大影響,編譯器會做相應(yīng)優(yōu)化。

        get操作

        ConcurrentHashMap的get操作是直接委托給Segment的get方法,直接看Segment的get方法:

        V?get(Object?key,?int?hash)?{??
        ?????if?(count?!=?0)?{?//?read-volatile?當前桶的數(shù)據(jù)個數(shù)是否為0?
        ?????????HashEntry?e?=?getFirst(hash);??得到頭節(jié)點
        ?????????while?(e?!=?null)?{??
        ?????????????if?(e.hash?==?hash?&&?key.equals(e.key))?{??
        ?????????????????V?v?=?e.value;??
        ?????????????????if?(v?!=?null)??
        ?????????????????????return?v;??
        ?????????????????return?readValueUnderLock(e);?//?recheck??
        ?????????????}??
        ?????????????e?=?e.next;??
        ?????????}??
        ?????}??
        ?????returnnull;??
        ?}

        get操作不需要鎖。

        除非讀到的值是空的才會加鎖重讀,我們知道HashTable容器的get方法是需要加鎖的,那么ConcurrentHashMap的get操作是如何做到不加鎖的呢?原因是它的get方法里將要使用的共享變量都定義成volatile

        第一步是訪問count變量,這是一個volatile變量,由于所有的修改操作在進行結(jié)構(gòu)修改時都會在最后一步寫count 變量,通過這種機制保證get操作能夠得到幾乎最新的結(jié)構(gòu)更新。對于非結(jié)構(gòu)更新,也就是結(jié)點值的改變,由于HashEntry的value變量是 volatile的,也能保證讀取到最新的值。

        接下來就是根據(jù)hash和key對hash鏈進行遍歷找到要獲取的結(jié)點,如果沒有找到,直接訪回null。對hash鏈進行遍歷不需要加鎖的原因在于鏈指針next是final的。但是頭指針卻不是final的,這是通過getFirst(hash)方法返回,也就是存在 table數(shù)組中的值。

        這使得getFirst(hash)可能返回過時的頭結(jié)點,例如,當執(zhí)行g(shù)et方法時,剛執(zhí)行完getFirst(hash)之后,另一個線程執(zhí)行了刪除操作并更新頭結(jié)點,這就導(dǎo)致get方法中返回的頭結(jié)點不是最新的。這是可以允許,通過對count變量的協(xié)調(diào)機制,get能讀取到幾乎最新的數(shù)據(jù),雖然可能不是最新的。要得到最新的數(shù)據(jù),只有采用完全的同步。

        最后,如果找到了所求的結(jié)點,判斷它的值如果非空就直接返回,否則在有鎖的狀態(tài)下再讀一次。這似乎有些費解,理論上結(jié)點的值不可能為空,這是因為 put的時候就進行了判斷,如果為空就要拋NullPointerException??罩档奈ㄒ辉搭^就是HashEntry中的默認值,因為 HashEntry中的value不是final的,非同步讀取有可能讀取到空值。

        仔細看下put操作的語句:tab[index] = new HashEntry(key, hash, first, value),在這條語句中,HashEntry構(gòu)造函數(shù)中對value的賦值以及對tab[index]的賦值可能被重新排序,這就可能導(dǎo)致結(jié)點的值為空。

        這里當v為空時,可能是一個線程正在改變節(jié)點,而之前的get操作都未進行鎖定,根據(jù)bernstein條件,讀后寫或?qū)懞笞x都會引起數(shù)據(jù)的不一致,所以這里要對這個e重新上鎖再讀一遍,以保證得到的是正確值。

        V?readValueUnderLock(HashEntry?e)?{??
        ?????lock();??
        ?????try?{??
        ?????????return?e.value;??
        ?????}?finally?{??
        ?????????unlock();??
        ?????}??
        ?}

        如用于統(tǒng)計當前Segement大小的count字段和用于存儲值的HashEntry的value。定義成volatile的變量,能夠在線程之間保持可見性,能夠被多線程同時讀,并且保證不會讀到過期的值,但是只能被單線程寫(有一種情況可以被多線程寫,就是寫入的值不依賴于原值),在get操作里只需要讀不需要寫共享變量count和value,所以可以不用加鎖。

        之所以不會讀到過期的值,是根據(jù)java內(nèi)存模型的happen before原則,對volatile字段的寫入操作先于讀操作,即使兩個線程同時修改和獲取volatile變量,get操作也能拿到最新的值,這是用volatile替換鎖的經(jīng)典應(yīng)用場景

        put操作

        同樣地put操作也是委托給段的put方法。下面是段的put方法:

        V?put(K?key,?int?hash,?V?value,?boolean?onlyIfAbsent)?{??
        ?????lock();??
        ?????try?{??
        ?????????int?c?=?count;??
        ?????????if?(c++?>?threshold)?//?ensure?capacity??
        ?????????????rehash();??
        ?????????HashEntry[]?tab?=?table;??
        ?????????int?index?=?hash?&?(tab.length?-?1);??
        ?????????HashEntry?first?=?tab[index];??
        ?????????HashEntry?e?=?first;??
        ?????????while?(e?!=?null?&&?(e.hash?!=?hash?||?!key.equals(e.key)))??
        ?????????????e?=?e.next;??
        ?????????V?oldValue;??
        ?????????if?(e?!=?null)?{??
        ?????????????oldValue?=?e.value;??
        ?????????????if?(!onlyIfAbsent)??
        ?????????????????e.value?=?value;??
        ?????????}??
        ?????????else?{??
        ?????????????oldValue?=?null;??
        ?????????????++modCount;??
        ?????????????tab[index]?=?new?HashEntry(key,?hash,?first,?value);??
        ?????????????count?=?c;?//?write-volatile??
        ?????????}??
        ?????????return?oldValue;??
        ?????}?finally?{??
        ?????????unlock();??
        ?????}??
        ?}

        該方法也是在持有段鎖(鎖定整個segment)的情況下執(zhí)行的,這當然是為了并發(fā)的安全,修改數(shù)據(jù)是不能并發(fā)進行的,必須得有個判斷是否超限的語句以確保容量不足時能夠rehash。

        接著是找是否存在同樣一個key的結(jié)點,如果存在就直接替換這個結(jié)點的值。否則創(chuàng)建一個新的結(jié)點并添加到hash鏈的頭部,這時一定要修改modCount和count的值,同樣修改count的值一定要放在最后一步。

        put方法調(diào)用了rehash方法,reash方法實現(xiàn)得也很精巧,主要利用了table的大小為2^n,這里就不介紹了。

        而比較難懂的是這句int index = hash & (tab.length - 1),原來segment里面才是真正的hashtable,即每個segment是一個傳統(tǒng)意義上的hashtable,如上圖,從兩者的結(jié)構(gòu)就可以看出區(qū)別,這里就是找出需要的entry在table的哪一個位置,之后得到的entry就是這個鏈的第一個節(jié)點,如果e!=null,說明找到了,這是就要替換節(jié)點的值(onlyIfAbsent == false),否則,我們需要new一個entry,它的后繼是first,而讓tab[index]指向它,什么意思呢?實際上就是將這個新entry插入到鏈頭,剩下的就非常容易理解了

        由于put方法里需要對共享變量進行寫入操作,所以為了線程安全,在操作共享變量時必須得加鎖。Put方法首先定位到Segment,然后在Segment里進行插入操作。插入操作需要經(jīng)歷兩個步驟,第一步判斷是否需要對Segment里的HashEntry數(shù)組進行擴容,第二步定位添加元素的位置然后放在HashEntry數(shù)組里。

        • 是否需要擴容。在插入元素前會先判斷Segment里的HashEntry數(shù)組是否超過容量(threshold),如果超過閥值,數(shù)組進行擴容。值得一提的是,Segment的擴容判斷比HashMap更恰當,因為HashMap是在插入元素后判斷元素是否已經(jīng)到達容量的,如果到達了就進行擴容,但是很有可能擴容之后沒有新元素插入,這時HashMap就進行了一次無效的擴容。

        • 如何擴容。擴容的時候首先會創(chuàng)建一個兩倍于原容量的數(shù)組,然后將原數(shù)組里的元素進行再hash后插入到新的數(shù)組里。為了高效ConcurrentHashMap不會對整個容器進行擴容,而只對某個segment進行擴容。

        另一個操作是containsKey,這個實現(xiàn)就要簡單得多了,因為它不需要讀取值:

        boolean?containsKey(Object?key,?int?hash)?{??
        ?????if?(count?!=?0)?{?//?read-volatile??
        ?????????HashEntry?e?=?getFirst(hash);??
        ?????????while?(e?!=?null)?{??
        ?????????????if?(e.hash?==?hash?&&?key.equals(e.key))??
        ?????????????????returntrue;??
        ?????????????e?=?e.next;??
        ?????????}??
        ?????}??
        ?????returnfalse;??
        ?}

        size()操作

        如果我們要統(tǒng)計整個ConcurrentHashMap里元素的大小,就必須統(tǒng)計所有Segment里元素的大小后求和。Segment里的全局變量count是一個volatile變量,那么在多線程場景下,我們是不是直接把所有Segment的count相加就可以得到整個ConcurrentHashMap大小了呢?

        不是的,雖然相加時可以獲取每個Segment的count的最新值,但是拿到之后可能累加前使用的count發(fā)生了變化,那么統(tǒng)計結(jié)果就不準了。所以最安全的做法,是在統(tǒng)計size的時候把所有Segment的put,remove和clean方法全部鎖住,但是這種做法顯然非常低效。

        因為在累加count操作過程中,之前累加過的count發(fā)生變化的幾率非常小,所以ConcurrentHashMap的做法是先嘗試2次通過不鎖住Segment的方式來統(tǒng)計各個Segment大小,如果統(tǒng)計的過程中,容器的count發(fā)生了變化,則再采用加鎖的方式來統(tǒng)計所有Segment的大小。

        那么ConcurrentHashMap是如何判斷在統(tǒng)計的時候容器是否發(fā)生了變化呢?使用modCount變量,在put , remove和clean方法里操作元素前都會將變量modCount進行加1,那么在統(tǒng)計size前后比較modCount是否發(fā)生變化,從而得知容器的大小是否發(fā)生變化。

        參考

        http://www.cnblogs.com/yydcdut/p/3959815.html


        瀏覽 61
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日韩A级片 | 好多水好爽小荡货好紧好热 | 性欧美free18hd喷潮 | 18 国产 免费 | 草逼的网站 | 午夜精品一区二区三区福利视频 | 国产美女爽爽爽 | 国产精品成人AV电影 | 俄罗斯freexxxx性 | 99国产一区 |