日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java 中 ConcurrentHashMap 原理分析

發(fā)布時(shí)間:2025/3/21 java 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 中 ConcurrentHashMap 原理分析 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一.Java并發(fā)基礎(chǔ)

當(dāng)一個(gè)對(duì)象或變量可以被多個(gè)線程共享的時(shí)候,就有可能使得程序的邏輯出現(xiàn)問(wèn)題。 在一個(gè)對(duì)象中有一個(gè)變量i=0,有兩個(gè)線程A,B都想對(duì)i加1,這個(gè)時(shí)候便有問(wèn)題顯現(xiàn)出來(lái),關(guān)鍵就是對(duì)i加1的這個(gè)過(guò)程不是原子操作。要想對(duì)i進(jìn)行遞增, 第一步就是獲取i的值,當(dāng)A獲取i的值為0,在A將新的值寫入A之前,B也獲取了A的值0,然后A寫入,i變成1,然后B也寫入i,i這個(gè)時(shí)候依然是1. 當(dāng)然java的內(nèi)存模型沒(méi)有上面這么簡(jiǎn)單,在Java Memory Model中,Memory分為兩類,main memory和working memory,main memory為所有線程共享,working memory中存放的是線程所需要的變量的拷貝(線程要對(duì)main memory中的內(nèi)容進(jìn)行操作的話,首先需要拷貝到自己的working memory,一般為了速度,working memory一般是在cpu的cache中的)。Volatile的變量在被操作的時(shí)候不會(huì)產(chǎn)生working memory的拷貝,而是直接操作main memory,當(dāng)然volatile雖然解決了變量的可見(jiàn)性問(wèn)題,但沒(méi)有解決變量操作的原子性的問(wèn)題,這個(gè)還需要synchronized或者CAS相關(guān)操作配合進(jìn)行。

多線程中幾個(gè)重要的概念:

可見(jiàn)性

也就說(shuō)假設(shè)一個(gè)對(duì)象中有一個(gè)變量i,那么i是保存在main memory中的,當(dāng)某一個(gè)線程要操作i的時(shí)候,首先需要從main memory中將i 加載到這個(gè)線程的working memory中,這個(gè)時(shí)候working memory中就有了一個(gè)i的拷貝,這個(gè)時(shí)候此線程對(duì)i的修改都在其working memory中,直到其將i從working memory寫回到main memory中,新的i的值才能被其他線程所讀取。從某個(gè)意義上說(shuō),可見(jiàn)性保證了各個(gè)線程的working memory的數(shù)據(jù)的一致性。 可見(jiàn)性遵循下面一些規(guī)則:

  • 當(dāng)一個(gè)線程運(yùn)行結(jié)束的時(shí)候,所有寫的變量都會(huì)被flush回main memory中。

  • 當(dāng)一個(gè)線程第一次讀取某個(gè)變量的時(shí)候,會(huì)從main memory中讀取最新的。

  • volatile的變量會(huì)被立刻寫到main memory中的,在jsr133中,對(duì)volatile的語(yǔ)義進(jìn)行增強(qiáng),后面會(huì)提到

  • 當(dāng)一個(gè)線程釋放鎖后,所有的變量的變化都會(huì)flush到main memory中,然后一個(gè)使用了這個(gè)相同的同步鎖的進(jìn)程,將會(huì)重新加載所有的使用到的變量,這樣就保證了可見(jiàn)性。

原子性

還拿上面的例子來(lái)說(shuō),原子性就是當(dāng)某一個(gè)線程修改i的值的時(shí)候,從取出i到將新的i的值寫給i之間不能有其他線程對(duì)i進(jìn)行任何操作。也就是說(shuō)保證某 個(gè)線程對(duì)i的操作是原子性的,這樣就可以避免數(shù)據(jù)臟讀。 通過(guò)鎖機(jī)制或者CAS(Compare And Set 需要硬件CPU的支持)操作可以保證操作的原子性。

有序性

假設(shè)在main memory中存在兩個(gè)變量i和j,初始值都為0,在某個(gè)線程A的代碼中依次對(duì)i和j進(jìn)行自增操作(i,j的操作不相互依賴)

i++;
j++;

由于,所以i,j修改操作的順序可能會(huì)被重新排序。那么修改后的ij寫到main memory中的時(shí)候,順序可能就不是按照i,j的順序了,這就是所謂的reordering,在單線程的情況下,當(dāng)線程A運(yùn)行結(jié)束的后i,j的值都加1 了,在線程自己看來(lái)就好像是線程按照代碼的順序進(jìn)行了運(yùn)行(這些操作都是基于as-if-serial語(yǔ)義的),即使在實(shí)際運(yùn)行過(guò)程中,i,j的自增可能 被重新排序了,當(dāng)然計(jì)算機(jī)也不能幫你亂排序,存在上下邏輯關(guān)聯(lián)的運(yùn)行順序肯定還是不會(huì)變的。但是在多線程環(huán)境下,問(wèn)題就不一樣了,比如另一個(gè)線程B的代碼 如下

  • if(j==1)?{?
  • ????System.out.println(i);?
  • }?
  • 按照我們的思維方式,當(dāng)j為1的時(shí)候那么i肯定也是1,因?yàn)榇a中i在j之前就自增了,但實(shí)際的情況有可能當(dāng)j為1的時(shí)候i還是為0。這就是 reordering產(chǎn)生的不好的后果,所以我們?cè)谀承r(shí)候?yàn)榱吮苊膺@樣的問(wèn)題需要一些必要的策略,以保證多個(gè)線程一起工作的時(shí)候也存在一定的次序。 JMM提供了happens-before 的排序策略。這樣我們可以得到多線程環(huán)境下的as-if-serial語(yǔ)義。 這里不對(duì)happens-before進(jìn)行詳細(xì)解釋了,詳細(xì)的請(qǐng)看這里http://www.ibm.com/developerworks/cn /java/j-jtp03304/,這里主要講一下volatile在新的java內(nèi)存模型下的變化,在jsr133之前,下面的代碼可能會(huì)出現(xiàn)問(wèn)題

  • Map?configOptions;?
  • char[]?configText;?
  • volatile?boolean?initialized?=?false;?
  • //?In?Thread?A?
  • configOptions?=?new?HashMap();?
  • configText?=?readConfigFile(fileName);?
  • processConfigOptions(configText,?configOptions);?
  • initialized?=?true;?
  • //?In?Thread?B?
  • while?(!initialized)?
  • ??sleep();?
  • //?use?configOptions?
  • jsr133之前,雖然對(duì) volatile 變量的讀和寫不能與對(duì)其他 volatile 變量的讀和寫一起重新排序,但是它們?nèi)匀豢梢耘c對(duì) nonvolatile 變量的讀寫一起重新排序,所以上面的Thread A的操作,就可能initialized變成true的時(shí)候,而configOptions還沒(méi)有被初始化,所以initialized先于 configOptions被線程B看到,就產(chǎn)生問(wèn)題了。

    JSR 133 Expert Group 決定讓 volatile 讀寫不能與其他內(nèi)存操作一起重新排序,新的內(nèi)存模型下,如果當(dāng)線程 A 寫入 volatile 變量 V 而線程 B 讀取 V 時(shí),那么在寫入 V 時(shí),A 可見(jiàn)的所有變量值現(xiàn)在都可以保證對(duì) B 是可見(jiàn)的。

    結(jié)果就是作用更大的 volatile 語(yǔ)義,代價(jià)是訪問(wèn) volatile 字段時(shí)會(huì)對(duì)性能產(chǎn)生更大的影響。這一點(diǎn)在ConcurrentHashMap中的統(tǒng)計(jì)某個(gè)segment元素個(gè)數(shù)的count變量中使用到了。

    二.線程安全的HashMap

    什么時(shí)候我們需要使用線程安全的hashmap呢,比如一個(gè)hashmap在運(yùn)行的時(shí)候只有讀操作,那么很明顯不會(huì)有問(wèn)題,但是當(dāng)涉及到同時(shí)有改變 也有讀的時(shí)候,就要考慮線程安全問(wèn)題了,在不考慮性能問(wèn)題的時(shí)候,我們的解決方案有Hashtable或者 Collections.synchronizedMap(hashMap),這兩種方式基本都是對(duì)整個(gè)hash表結(jié)構(gòu)做鎖定操作的,這樣在鎖表的期間, 別的線程就需要等待了,無(wú)疑性能不高。

    三.ConcurrentHashMap實(shí)現(xiàn)原理

    數(shù)據(jù)結(jié)構(gòu) ConcurrentHashMap的目標(biāo)是實(shí)現(xiàn)支持高并發(fā)、高吞吐量的線程安全的HashMap。當(dāng)然不能直接對(duì)整個(gè)hashtable加鎖,所以在ConcurrentHashMap中,數(shù)據(jù)的組織結(jié)構(gòu)和HashMap有所區(qū)別。

    一個(gè)ConcurrentHashMap由多個(gè)segment組成,每一個(gè)segment都包含了一個(gè)HashEntry數(shù)組的 hashtable, 每一個(gè)segment包含了對(duì)自己的hashtable的操作,比如get,put,replace等操作,這些操作發(fā)生的時(shí)候,對(duì)自己的 hashtable進(jìn)行鎖定。由于每一個(gè)segment寫操作只鎖定自己的hashtable,所以可能存在多個(gè)線程同時(shí)寫的情況,性能無(wú)疑好于只有一個(gè) hashtable鎖定的情況。

    源碼分析 在ConcurrentHashMap的remove,put操作還是比較簡(jiǎn)單的,都是將remove或者put操作交給key所對(duì)應(yīng)的segment去做的,所以當(dāng)幾個(gè)操作不在同一個(gè)segment的時(shí)候就可以并發(fā)的進(jìn)行。

  • public?V?remove(Object?key)?{?
  • ????int?hash?=?hash(key.hashCode());?
  • ????????return?segmentFor(hash).remove(key,?hash,?null);?
  • ????}?
  • 而segment中的remove操作除了加鎖之外和HashMap中的remove操作基本無(wú)異。

  • /**?
  • ?????????*?Remove;?match?on?key?only?if?value?null,?else?match?both.?
  • ?????????*/?
  • ????????V?remove(Object?key,?int?hash,?Object?value)?{?
  • ????????????lock();?
  • ????????????try?{?
  • ????????????????int?c?=?count?-?1;?
  • ????????????????HashEntry<K,V>[]?tab?=?table;?
  • ????????????????int?index?=?hash?&?(tab.length?-?1);?
  • ????????????????HashEntry<K,V>?first?=?tab[index];?
  • ????????????????HashEntry<K,V>?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<K,V>?newFirst?=?e.next;?
  • ????????????????????????for?(HashEntry<K,V>?p?=?first;?p?!=?e;?p?=?p.next)?
  • ????????????????????????????newFirst?=?new?HashEntry<K,V>(p.key,?p.hash,?
  • ??????????????????????????????????????????????????????????newFirst,?p.value);?
  • ????????????????????????tab[index]?=?newFirst;?
  • ????????????????????????count?=?c;?//?write-volatile?
  • ????????????????????}?
  • ????????????????}?
  • ????????????????return?oldValue;?
  • ????????????}?finally?{?
  • ????????????????unlock();?
  • ????????????}?
  • ????????}?
  • 上面的代碼中關(guān)于volatile類型的變量count值得一提,這里充分利用了Java 5中對(duì)volatile語(yǔ)義的增強(qiáng),count = c的操作必須在modCount,table等操作的后面,這樣才能保證這些變量操作的可見(jiàn)性。 Segment類繼承于ReentrantLock,主要是為了使用ReentrantLock的鎖,ReentrantLock的實(shí)現(xiàn)比 synchronized在多個(gè)線程爭(zhēng)用下的總體開(kāi)銷小。 put操作和remove操作類似。

    接下來(lái)我們來(lái)看下get操作。

  • public?V?get(Object?key)?{?
  • ????????int?hash?=?hash(key.hashCode());?
  • ????????return?segmentFor(hash).get(key,?hash);?
  • ????}?
  • ?
  • 也是使用了對(duì)應(yīng)的segment的get?
  • ?
  • V?get(Object?key,?int?hash)?{?
  • ????????????if?(count?!=?0)?{?//?read-volatile?
  • ????????????????HashEntry<K,V>?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;?
  • ????????}?
  • 上面的代碼中,一開(kāi)始就對(duì)volatile變量count進(jìn)行了讀取比較,這個(gè)還是java5對(duì)volatile語(yǔ)義增強(qiáng)的作用,這樣就可以獲取變 量的可見(jiàn)性。所以count != 0之后,我們可以認(rèn)為對(duì)應(yīng)的hashtable是最新的,當(dāng)然由于讀取的時(shí)候沒(méi)有加鎖,在get的過(guò)程中,可能會(huì)有更新。當(dāng)發(fā)現(xiàn)根據(jù)key去找元素的時(shí) 候,但發(fā)現(xiàn)找得的key對(duì)應(yīng)的value為null,這個(gè)時(shí)候可能會(huì)有其他線程正在對(duì)這個(gè)元素進(jìn)行寫操作,所以需要在使用鎖的情況下在讀取一下 value,以確保最終的值。

    其他相關(guān)涉及讀取的操作也都類似。

    from:?http://developer.51cto.com/art/201506/479724.htm

    總結(jié)

    以上是生活随笔為你收集整理的Java 中 ConcurrentHashMap 原理分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 艳妇乳肉豪妇荡乳 | 六月激情婷婷 | 成人动漫视频在线观看 | 成人xxxx| 校园春色亚洲色图 | 美美女高清毛片视频免费观看 | 五月婷婷开心中文字幕 | 天堂va欧美va亚洲va老司机 | 91福利视频在线观看 | 亚洲视频久久久 | 成人h动漫精品一区二区无码 | av超碰| 国产l精品国产亚洲区久久 午夜青青草 | 日本人妻换人妻毛片 | 国内精品视频在线播放 | av一区二区三区免费观看 | 国产三级福利 | 精品成人一区二区三区 | 国产三级日本三级在线播放 | 熟女一区二区三区四区 | 中文字幕一区二区在线视频 | 性欢交69精品久久久 | 高清久久久 | 五级 黄 色 片| 日韩特黄一级片 | 国产精品一品 | 国产精品午夜一区二区 | 绿帽在线 | 国产成人精品一区二区三 | 久久国内精品 | 天天干天天干天天 | 中文字幕在线看高清电影 | 在线免费视频你懂的 | 波多一区 | 国产视频在线观看网站 | 国产色诱视频 | 九九天堂 | 免费中文字幕日韩 | 欧美不卡一区二区三区 | 亚洲欧美日韩另类 | 国产白袜脚足j棉袜在线观看 | 三区在线视频 | 50度灰在线 | 欧美整片在线 | 五月激情开心网 | 王者后宫yin肉h文催眠 | 欧美丰满熟妇xxxx | 国产无遮挡又黄又爽 | 久久夜色精品国产欧美乱极品 | 免费污污视频在线观看 | 99久久久无码国产精品性黑人 | 欧美无砖区 | 狠狠干影视 | 国产精品国产三级国产普通话蜜臀 | 成人精品国产免费网站 | 日韩av一区二区在线播放 | 久草综合视频 | 一级特级毛片 | 日中文字幕 | 龚玥菲三级露全乳视频 | 亚洲欧美日韩国产精品 | 涩涩网站视频 | 超碰p| 亚洲最大福利视频 | 手机在线看片日韩 | 国产三级不卡 | 国产日韩欧美一二三区 | 丁香花五月 | 欧美影院久久 | 精品国产av色一区二区深夜久久 | 丰满岳乱妇在线观看中字无码 | 怡红院成永久免费人全部视频 | 欲求不满在线小早川怜子 | 亚洲精品乱码久久久久久蜜桃麻豆 | 天堂网免费视频 | 风流僵尸艳片a级 | 亚洲免费在线观看视频 | 999国产精品视频免费 | av基地 | 亚洲天堂偷拍 | 久草久操| 91视频免费视频 | 日操操 | 色婷婷丁香| 妺妺窝人体色www聚色窝仙踪 | 动漫精品一区 | 欧美视频免费 | 国产精品视频免费在线观看 | 91视频免费| 黄色av网页 | 久插网| 操干网 | 欧美香蕉在线 | 极品少妇xxxx | 国产成人在线视频观看 | 操碰视频在线 | 色一情一乱一乱一区91av | www.黄色小说.com | 亚洲精品国产精品国自产网站按摩 |