目標(biāo):
1.capacity,concurrencyLevel:容量與并發(fā)級(jí)別的偿衰,如何促發(fā)擴(kuò)容狗超,ConcurrentHashMap兩個(gè)字段的意義:segmentMask,segmentShift
2.jdk1.6/jdk1.7不同之處,jdk1.6
count是volitile的
3.理解ConcurrentHashMap為何支持高并發(fā)谴忧,對(duì)此作了哪些優(yōu)化很泊,segment, volitile, happens-before, HashEntry final value?
4.記住默認(rèn)容量、并發(fā)級(jí)別沾谓、加載因子
5.測(cè)試不同并發(fā)級(jí)別下的性能測(cè)試委造,出測(cè)試結(jié)果
6.實(shí)現(xiàn)一個(gè)簡(jiǎn)易的ConcurrentHashMap示例
總結(jié):
1、字段的含義:
1.1 capacity是指整個(gè)map的容量均驶,即所有segment中的hashEntry的數(shù)昏兆,map最終都是在hashEntry的中存儲(chǔ)數(shù)據(jù)的
1.2 concurrencyLevel:并發(fā)級(jí)別,即ConcurrentHashMap中segment的數(shù)量妇穴,concurrencyLevel = segmentSize爬虱,并發(fā)時(shí)鎖的即是segment隶债,segmentSize 必須是 2^n ,可以參考HashMap的size也是必須是2^n
1.3 segmentShift,segmentMask:segment偏移量,segment掩碼跑筝,在segmentFor時(shí)用到死讹,定位到具體某個(gè)segment。segmentMask = segmentSize - 1曲梗,segmentShift= 32 - sshift,
sshift=n,n是segmentSize =
2^n中的n
如concurrencyLevel = 16 => sshift=4,segmentMask=15 =>
segmentShift= 32 - 4赞警,再哈希后的數(shù)最大是32位二進(jìn)制數(shù)據(jù),所以是32 -
sshift虏两,segmentFor:
hash >>> segmentShift & segmentMask 意思是讓高4位參與到hash運(yùn)算中
2愧旦、jdk1.6/jdk1.7: https://my.oschina.net/hosee/blog/675884
最大的區(qū)別在于,jdk1.6應(yīng)用volatile關(guān)鍵字 happens-before原則實(shí)現(xiàn)的讀/寫(xiě)一直定罢,jdk1.6中用的UNSAFE的原子操作來(lái)實(shí)現(xiàn)的
3笤虫、高并發(fā)的原理
3.1 分離鎖
無(wú)需鎖整個(gè)ConcurrentHashMap,只在修改結(jié)構(gòu)的時(shí)候祖凫,只需鎖住分段segment
3.2 用 volatile 變量協(xié)調(diào)讀寫(xiě)線(xiàn)程間的內(nèi)存可見(jiàn)性琼蚯,使得讀(get)可以不加鎖
讀操作的高效之處在于整個(gè)get過(guò)程不需要加鎖,除非讀到的值是空的才會(huì)加鎖重讀蝙场。get操作是如何做到不加鎖的呢凌停?原因是它的get方法里將要使用的共享變量都定義成volatile,如用于統(tǒng)計(jì)當(dāng)前Segement大小的?????? count字段和用于存儲(chǔ)值的HashEntry的value售滤。定義成volatile的變量罚拟,能夠在線(xiàn)程之間保持可見(jiàn)性,能夠被多線(xiàn)程同時(shí)讀完箩,并且保證不會(huì)讀到過(guò)期的值赐俗,但是只能被單線(xiàn)程寫(xiě)(有一種情況可以被多線(xiàn)程寫(xiě),就是寫(xiě)入的值不依賴(lài)于原值)弊知,在get操作里只需要讀不需要寫(xiě)共享變量count和value阻逮,所以可以不用加鎖。之所以不會(huì)讀到過(guò)期的值秩彤,是根據(jù)java內(nèi)存模型的happen before原則叔扼,對(duì)volatile字段的寫(xiě)入操作先于讀操作,即使兩個(gè)線(xiàn)程同時(shí)修改和獲取volatile變量漫雷,get操作也能拿到最新的值瓜富,這是用volatile替換鎖的經(jīng)典應(yīng)用場(chǎng)景(這是JDK1.6的實(shí)現(xiàn))transient
volatile int count;volatile V value;
3.3 用 HashEntery 對(duì)象的不變性來(lái)降低讀操作對(duì)加鎖的需求
HashEntry 中的 key,hash降盹,next 都聲明為 final与柑。這意味著,不能把節(jié)點(diǎn)添加到鏈接的中間和尾部,也不能在鏈接的中間和尾部刪除節(jié)點(diǎn)价捧。這個(gè)特性可以保證:在訪(fǎng)問(wèn)某個(gè)節(jié)點(diǎn)時(shí)丑念,這個(gè)節(jié)點(diǎn)之后的鏈接不會(huì)被改變。這個(gè)特性可以大大降低處理鏈表時(shí)的復(fù)雜性结蟋。
3.4 總結(jié)
基于通常情形而優(yōu)化:
在實(shí)際的應(yīng)用中脯倚,散列表一般的應(yīng)用場(chǎng)景是:除了少數(shù)插入操作和刪除操作外,絕大多數(shù)都是讀取操作椎眯,而且讀操作在大多數(shù)時(shí)候都是成功的挠将。正是基于這個(gè)前提,ConcurrentHashMap 針對(duì)讀操作做了大量的優(yōu)化编整。通過(guò) HashEntry 對(duì)象的不變性和用 volatile型變量協(xié)調(diào)線(xiàn)程間的內(nèi)存可見(jiàn)性,使得大多數(shù)時(shí)候乳丰,讀操作不需要加鎖就可以正確獲得值掌测。這兩個(gè)特性相配合,不僅減少了請(qǐng)求同一個(gè)鎖的頻率(讀操作一般不需要加鎖就能夠成功獲得值)产园,也減少了持有同一個(gè)鎖的時(shí)間(只有讀到 value 域的值為 null 時(shí) , 讀線(xiàn)程才需要加鎖后重讀)
ConcurrentHashMap 的高并發(fā)性主要來(lái)自于三個(gè)方面:
用分離鎖實(shí)現(xiàn)多個(gè)線(xiàn)程間的更深層次的共享訪(fǎng)問(wèn)汞斧。
用 HashEntery 對(duì)象的不變性來(lái)降低執(zhí)行讀操作的線(xiàn)程在遍歷鏈表期間對(duì)加鎖的需求。
通過(guò)對(duì)同一個(gè)Volatile 變量的寫(xiě) / 讀訪(fǎng)問(wèn)什燕,協(xié)調(diào)不同線(xiàn)程間讀/ 寫(xiě)操作的內(nèi)存可見(jiàn)性
4粘勒、默認(rèn)容量:
DEFAULT_INITIAL_CAPACITY
= 16;
DEFAULT_CONCURRENCY_LEVEL
= 16;
DEFAULT_LOAD_FACTOR = 0.75f;
MAXIMUM_CAPACITY
= 1 << 30;
MAX_SEGMENTS =
1 << 16;
5、屎即?Google ConcurrentHashMapperformancetest
6庙睡、?
參考:
Jdk1.6
http://www.infoq.com/cn/articles/ConcurrentHashMap/
https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/
jdk1.7