成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

為什么ConcurrentHashMap是弱一致的

CarterLi / 2137人閱讀

摘要:是我們一直擁有的,即我們有,。中的迭代器中的迭代器主要包括方法。在遍歷過(guò)程中,如果已經(jīng)遍歷的數(shù)組上的內(nèi)容變化了,迭代器不會(huì)拋出異常。這就是迭代器弱一致的表現(xiàn)。總結(jié)的弱一致性主要是為了提升效率,是一致性與效率之間的一種權(quán)衡。

本文將用到Java內(nèi)存模型的happens-before偏序關(guān)系(下文將簡(jiǎn)稱為hb)以及ConcurrentHashMap的底層模型相關(guān)的知識(shí)。happens-before相關(guān)內(nèi)容參見(jiàn):JLS §17.4.5. Happens-before Order、深入理解Java內(nèi)存模型以及Happens before;ConcurrentHashMap的詳細(xì)介紹以及底層原理見(jiàn)深入分析ConcurrentHashMap。本文將從ConcurrentHashMap的get,clear,iterator(entrySet、keySet、values方法)三個(gè)方法來(lái)分析它們的弱一致問(wèn)題。

ConcurrentHashMap#get

get方法是弱一致的,是什么含義?可能你期望往ConcurrentHashMap底層數(shù)據(jù)結(jié)構(gòu)中加入一個(gè)元素后,立馬能對(duì)get可見(jiàn),但ConcurrentHashMap并不能如你所愿。換句話說(shuō),put操作將一個(gè)元素加入到底層數(shù)據(jù)結(jié)構(gòu)后,get可能在某段時(shí)間內(nèi)還看不到這個(gè)元素,若不考慮內(nèi)存模型,單從代碼邏輯上來(lái)看,卻是應(yīng)該可以看得到的。

下面將結(jié)合代碼和java內(nèi)存模型相關(guān)內(nèi)容來(lái)分析下put/get方法(本文中所有ConcurrentHashMap相關(guān)的代碼均來(lái)自hotspot1.6.0_18)。put方法我們只需關(guān)注Segment#put,get方法只需關(guān)注Segment#get,在繼續(xù)之前,先要說(shuō)明一下Segment里有兩個(gè)volatile變量:counttable;HashEntry里有一個(gè)volatile變量:value
Segment#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#get

V get(Object key, int hash) {
    if (count != 0) { // read-volatile
        HashEntry e = getFirst(hash);
        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;
        }
    }
    return null;
}

我們?nèi)绾未_定線程1放入某個(gè)變量的值是否對(duì)線程2可見(jiàn)?文章開(kāi)頭提到的JLS鏈接中有說(shuō)到,當(dāng)a hb c時(shí),a對(duì)c可見(jiàn),那么我們接下來(lái)我們只要尋找put和get之間所有可能的執(zhí)行軌跡上的hb關(guān)系。要找出hb關(guān)系,我們需要先找出與hb相關(guān)的Action。為方便,這里將兩段代碼放到了一張圖片上。

可以注意到,同一個(gè)Segment實(shí)例中的put操作是加了鎖的,而對(duì)應(yīng)的get卻沒(méi)有。根據(jù)hb關(guān)系中的線程間Action類別,可以從上圖中找出這些Action,主要是volatile讀寫(xiě)和加解鎖,也就是圖中畫(huà)了橫線的那些。

put操作可以分為兩種情況,一是key已經(jīng)存在,修改對(duì)應(yīng)的value;二是key不存在,將一個(gè)新的Entry加入底層數(shù)據(jù)結(jié)構(gòu)。

key已經(jīng)存在的情況比較簡(jiǎn)單,即if (e != null)部分,前面已經(jīng)說(shuō)過(guò)HashEntry的value是個(gè)volatile變量,當(dāng)線程1給value賦值后,會(huì)立馬對(duì)執(zhí)行g(shù)et的線程2可見(jiàn),而不用等到put方法結(jié)束。

key不存在的情況稍微復(fù)雜一些,新加一個(gè)Entry的邏輯在else中。那么將new HashEntry賦值給tab[index]是否能立刻對(duì)執(zhí)行g(shù)et的線程可見(jiàn)呢?我們只需分析寫(xiě)tab[index]與讀取tab[index]之間是否有hb關(guān)系即可。

假設(shè)執(zhí)行put的線程與執(zhí)行g(shù)et的線程的軌跡是這樣的

執(zhí)行put的線程 執(zhí)行g(shù)et的線程
⑧tab[index] = new HashEntry(key, hash, first, value)
②count = c
③if (count != 0)
⑨HashEntry e = getFirst(hash);

tab變量是一個(gè)普通的變量,雖然給它賦值的是volatile的table。另外,雖然引用類型(數(shù)組類型)的變量table是volatile的,但table中的元素不是volatile的,因此⑧只是一個(gè)普通的寫(xiě)操作;count變量是volatile的,因此②是一個(gè)volatile寫(xiě);③很顯然是一個(gè)volatile讀;⑨中g(shù)etFirst方法中讀取了table,因此包含一個(gè)volatile讀。

根據(jù)Synchronization Order,對(duì)同一個(gè)volatile變量,有volatile寫(xiě) hb volatile讀。在這個(gè)執(zhí)行軌跡中,時(shí)間上②在③之前發(fā)生,且②是寫(xiě)count,③是讀count,都是針對(duì)同一個(gè)volatile變量count,因此有② hb ③;又因?yàn)棰嗪廷谑峭粋€(gè)線程中的,③和⑨是同一個(gè)線程中的,根據(jù)Program Order,有⑧ hb ②,③ hb ⑨。目前我們有了三組關(guān)系了⑧ hb ②,② hb ③,③ hb ⑨,再根據(jù)hb關(guān)系是可傳遞的(即若有x hb y且y hb z,可得出x hb z),可以得出⑧ hb ⑨。因此,如果按照上述執(zhí)行軌跡,⑧中寫(xiě)入的數(shù)組元素對(duì)⑨中的讀取操作是可見(jiàn)的。

再考慮這樣一個(gè)執(zhí)行軌跡:

|執(zhí)行put的線程|執(zhí)行g(shù)et的線程|
|-|-|
|⑧tab[index] = new HashEntry(key, hash, first, value)||
||③if (count != 0)|
②count = c||
||⑨HashEntry e = getFirst(hash);|

這里只是變換了下執(zhí)行順序。每條語(yǔ)句的volatile讀寫(xiě)含義同上,但它們之間的hb關(guān)系卻改變了。Program Order是我們一直擁有的,即我們有⑧ hb ②,③ hb ⑨。但這次對(duì)volatile的count的讀時(shí)間上發(fā)生在對(duì)count的寫(xiě)之前,我們無(wú)法得出② hb ⑨這層關(guān)系了。因此,通過(guò)count變量,在這個(gè)軌跡上是無(wú)法得出⑧ hb ⑨的。那么,存不存在其它可替換關(guān)系,讓我們?nèi)阅艿贸觫?hb ⑨呢?

我們要找的是,在⑧之后有一條語(yǔ)句或指令x,在⑨之前有一條語(yǔ)句或指令y,存在x hb y。這樣我們可以有⑧ hb x,x hb y, y hb ⑨。就讓我們來(lái)找一下是否存在這樣的x和y。圖中的⑤、⑥、⑦、①存在volatile讀寫(xiě),但是它們?cè)冖嘀?,因此?duì)確立⑧ hb ⑨這個(gè)關(guān)系沒(méi)有用處;同理,④在⑨之后,我們要找的是⑨之前的,因此也對(duì)這個(gè)問(wèn)題無(wú)益。前面已經(jīng)分析過(guò)了②,③之間沒(méi)法確立hb關(guān)系。

在⑧之后,我們發(fā)現(xiàn)一個(gè)unlock操作,如果能在⑨之前找到一個(gè)lock操作,那么我們要找的x就是unlock,要找的y就是lock,因?yàn)镾ynchronization Order中有unlock hb lock的關(guān)系。但是,很不幸運(yùn),⑨之前沒(méi)有l(wèi)ock操作。因此,對(duì)于這樣的軌跡,是沒(méi)有⑧ hb
⑨關(guān)系的,也就是說(shuō),如果某個(gè)Segment實(shí)例中的put將一個(gè)Entry加入到了table中,在未執(zhí)行count賦值操作之前有另一個(gè)線程執(zhí)行了同一個(gè)Segment實(shí)例中的get,來(lái)獲取這個(gè)剛加入的Entry中的value,那么是有可能取不到的!

此外,如果getFirst(hash)先執(zhí)行,tab[index] = new HashEntry(key, hash, first, value)后執(zhí)行,那么,這個(gè)get操作也是看不到put的結(jié)果的。

正是因?yàn)間et操作幾乎所有時(shí)候都是一個(gè)無(wú)鎖操作(get中有一個(gè)readValueUnderLock調(diào)用,不過(guò)這句執(zhí)行到的幾率極小),使得同一個(gè)Segment實(shí)例上的put和get可以同時(shí)進(jìn)行,這就是get操作是弱一致的根本原因。Java
API中對(duì)此有一句簡(jiǎn)單的描述:

  

Retrievals reflect the results of the most recently completed
update operations holding upon their onset.

也就是說(shuō)API上保證get操作一定能看到已完成的put操作。已完成的put操作肯定在get讀取count之前對(duì)count做了寫(xiě)入操作。因此,也就是我們第一個(gè)軌跡分析的情況。

ConcurrentHashMap#clear

clear方法很簡(jiǎn)單,看下代碼即知。

public void clear() {
    for (int i = 0; i < segments.length; ++i)
        segments[i].clear();
}

因?yàn)闆](méi)有全局的鎖,在清除完一個(gè)segments之后,正在清理下一個(gè)segments的時(shí)候,已經(jīng)清理segments可能又被加入了數(shù)據(jù),因此clear返回的時(shí)候,ConcurrentHashMap中是可能存在數(shù)據(jù)的。因此,clear方法是弱一致的。

ConcurrentHashMap中的迭代器

ConcurrentHashMap中的迭代器主要包括entrySet、keySet、values方法。它們大同小異,這里選擇entrySet解釋。當(dāng)我們調(diào)用entrySet返回值的iterator方法時(shí),返回的是EntryIterator,在EntryIterator上調(diào)用next方法時(shí),最終實(shí)際調(diào)用到了HashIterator.advance()方法,看下這個(gè)方法:

final void advance() {
    if (nextEntry != null && (nextEntry = nextEntry.next) != null)
        return;

    while (nextTableIndex >= 0) {
        if ( (nextEntry = currentTable[nextTableIndex--]) != null)
            return;
    }

    while (nextSegmentIndex >= 0) {
        Segment seg = segments[nextSegmentIndex--];
        if (seg.count != 0) {
            currentTable = seg.table;
            for (int j = currentTable.length - 1; j >= 0; --j) {
                if ( (nextEntry = currentTable[j]) != null) {
                    nextTableIndex = j - 1;
                    return;
                }
            }
        }
    }
}

這個(gè)方法在遍歷底層數(shù)組。在遍歷過(guò)程中,如果已經(jīng)遍歷的數(shù)組上的內(nèi)容變化了,迭代器不會(huì)拋出ConcurrentModificationException異常。如果未遍歷的數(shù)組上的內(nèi)容發(fā)生了變化,則有可能反映到迭代過(guò)程中。這就是ConcurrentHashMap迭代器弱一致的表現(xiàn)。

總結(jié)

ConcurrentHashMap的弱一致性主要是為了提升效率,是一致性與效率之間的一種權(quán)衡。要成為強(qiáng)一致性,就得到處使用鎖,甚至是全局鎖,這就與Hashtable和同步的HashMap一樣了。


via ifeve.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/64041.html

相關(guān)文章

  • java中ConcurrentHashMap使用及在Java 8中沖突方案

    摘要:中的使用及在中的沖突方案引言簡(jiǎn)稱是在作為的替代選擇新引入的,是包的重要成員。為了解決在頻繁沖突時(shí)性能降低的問(wèn)題,中使用平衡樹(shù)來(lái)替代鏈表存儲(chǔ)沖突的元素。目前,只有和會(huì)在頻繁沖突的情況下使用平衡樹(shù)。 java中ConcurrentHashMap的使用及在Java 8中的沖突方案 1、引言 ConcurrentHashMap(簡(jiǎn)稱CHM)是在Java 1.5作為Hashtable的替代選擇新...

    kun_jian 評(píng)論0 收藏0
  • Java多線程之線程安全與異步執(zhí)行

    摘要:同步包裝器任何集合類使用同步包裝器都會(huì)變成線程安全的,會(huì)將集合的方法使用鎖加以保護(hù),保證線程的安全訪問(wèn)。線程池中的線程執(zhí)行完畢并不會(huì)馬上死亡,而是在池中準(zhǔn)備為下一個(gè)請(qǐng)求提供服務(wù)。 多線程并發(fā)修改一個(gè)數(shù)據(jù)結(jié)構(gòu),很容易破壞這個(gè)數(shù)據(jù)結(jié)構(gòu),如散列表。鎖能夠保護(hù)共享數(shù)據(jù)結(jié)構(gòu),但選擇線程安全的實(shí)現(xiàn)更好更容易,如阻塞隊(duì)列就是線程安全的集合。 線程安全的集合 Vector和HashTable類提供了線...

    taoszu 評(píng)論0 收藏0
  • ConcurrentHashMap探究

    摘要:是線程安全,性能出色的的線程安全實(shí)現(xiàn),相比較他是線程安全的,相比較他的性能優(yōu)勢(shì)非常明顯。的源碼其實(shí)很簡(jiǎn)單,和的結(jié)構(gòu)一致,但是每一個(gè)方法都是用來(lái)修飾,以保證操作是線程安全的。這樣在多線程的情況下,只有一個(gè)線程獲取鎖操作中的數(shù)據(jù)。 ConcurrentHashMap ConcurrentHashMap是線程安全,性能出色的Map的線程安全實(shí)現(xiàn),相比較HashMap他是線程安全的,相比較Ha...

    zombieda 評(píng)論0 收藏0
  • ConcurrentHashMap學(xué)習(xí)

    摘要:與和是一一對(duì)應(yīng)的,對(duì)充當(dāng)鎖的角色,每當(dāng)對(duì)數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),首先要獲取對(duì)應(yīng)的鎖解決散列沖突的方式是采用分離鏈表法分散鏈表法使用鏈表解決沖突,將散列值相同的元素都保存到一個(gè)鏈表中。負(fù)載因子,默認(rèn)為。 一、為什么要用ConcurrentHashMap? 1、HashMap線程不安全,并且進(jìn)行put操作會(huì)導(dǎo)致死循環(huán)(由于HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu),Entry下的next...

    mayaohua 評(píng)論0 收藏0
  • 通俗方式理解動(dòng)態(tài)類型,靜態(tài)類型;強(qiáng)類型,弱類型

    摘要:不允許隱式轉(zhuǎn)換的是強(qiáng)類型,允許隱式轉(zhuǎn)換的是弱類型。拿一段代碼舉例在使用調(diào)用函數(shù)的時(shí)候會(huì)先生成一個(gè)類模板運(yùn)行時(shí)生成,執(zhí)行的時(shí)候會(huì)生成類模板,執(zhí)行的時(shí)候會(huì)生成類模板。 0 x 01 引言 今天和一個(gè)朋友討論 C++ 是強(qiáng)類型還是弱類型的時(shí)候,他告訴我 C++ 是強(qiáng)類型的,他和我說(shuō)因?yàn)?C++ 在寫(xiě)的時(shí)候需要 int,float 等等關(guān)鍵字去定義變量,因此 C++ 是強(qiáng)類型的,我告訴他 C+...

    周?chē)?guó)輝 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<