一、Vector和SynchronizedList
1.1回顧線程安全的Vector和SynchronizedList
我們知道ArrayList是用于替代Vector的卵酪,Vector是線程安全的容器密末。因為它幾乎在每個方法聲明處都加了synchronized關(guān)鍵字來使容器安全仔涩。
如果使用Collections.synchronizedList(new ArrayList())來使ArrayList變成是線程安全的話饲常,也是幾乎都是每個方法都加上synchronized關(guān)鍵字的,只不過它不是加在方法的聲明處黍翎,而是方法的內(nèi)部。
二蟆技、CopyOnWriteArrayList(Set)介紹
一般來說玩敏,我們會認(rèn)為:CopyOnWriteArrayList是同步List的替代品,CopyOnWriteArraySet是同步Set的替代品质礼。
無論是Hashtable-->ConcurrentHashMap旺聚,還是說Vector-->CopyOnWriteArrayList。JUC下支持并發(fā)的容器與老一代的線程安全類相比眶蕉,總結(jié)起來就是加鎖粒度的問題
Hashtable砰粹、Vector加鎖的粒度大(直接在方法聲明處使用synchronized)
ConcurrentHashMap、CopyOnWriteArrayList加鎖粒度小(用各種的方式來實現(xiàn)線程安全,比如我們知道的ConcurrentHashMap用了cas鎖碱璃、volatile等方式來實現(xiàn)線程安全..)
JUC下的線程安全容器在遍歷的時候不會拋出ConcurrentModificationException異常
所以一般來說弄痹,我們都會使用JUC包下給我們提供的線程安全容器,而不是使用老一代的線程安全容器嵌器。
下面我們來看看CopyOnWriteArrayList是怎么實現(xiàn)的肛真,為什么使用迭代器遍歷的時候就不用額外加鎖,也不會拋出ConcurrentModificationException異常爽航。
2.1CopyOnWriteArrayList實現(xiàn)原理
我們還是先來回顧一下COW:
如果有多個調(diào)用者(callers)同時請求相同資源(如內(nèi)存或磁盤上的數(shù)據(jù)存儲)蚓让,他們會共同獲取相同的指針指向相同的資源,直到某個調(diào)用者試圖修改資源的內(nèi)容時讥珍,系統(tǒng)才會真正復(fù)制一份專用副本(private copy)給該調(diào)用者历极,而其他調(diào)用者所見到的最初的資源仍然保持不變。優(yōu)點是如果調(diào)用者沒有修改該資源衷佃,就不會有副本(private copy)被建立趟卸,因此多個調(diào)用者只是讀取操作時可以共享同一份資源。
參考自維基百科:zh.wikipedia.org/wiki/%E5%AF…
概括一下CopyOnWriteArrayList源碼注釋介紹了什么:
(1)0CopyOnWriteArrayList是線程安全容器(相對于ArrayList)氏义,底層通過復(fù)制數(shù)組的方式來實現(xiàn)锄列。
(2)CopyOnWriteArrayList在遍歷的使用不會拋出ConcurrentModificationException異常,并且遍歷的時候就不用額外加鎖
(3)元素可以為null
2.1.1看一下CopyOnWriteArrayList基本的結(jié)構(gòu)
看起來挺簡單的觅赊,CopyOnWriteArrayList底層就是數(shù)組右蕊,加鎖就交由ReentrantLock來完成。
2.1.2常見方法的實現(xiàn)
根據(jù)上面的分析我們知道如果遍歷Vector/SynchronizedList是需要自己手動加鎖的吮螺。
CopyOnWriteArrayList使用迭代器遍歷時不需要顯示加鎖饶囚,看看add()、clear()鸠补、remove()與get()方法的實現(xiàn)可能就有點眉目了萝风。
首先我們可以看看add()方法
通過代碼我們可以知道:在添加的時候就上鎖,并復(fù)制一個新數(shù)組紫岩,增加操作在新數(shù)組上完成规惰,將array指向到新數(shù)組中,最后解鎖泉蝌。
再來看看size()方法:
再來看看get()方法:
那再來看看set()方法
了歇万。
總結(jié):
(1)在修改時,復(fù)制出一個新數(shù)組勋陪,修改的操作在新數(shù)組中完成贪磺,最后將新數(shù)組交由array變量指向。
(2)寫加鎖诅愚,讀不加鎖
2.1.3剖析為什么遍歷時不用調(diào)用者顯式加鎖
常用的方法實現(xiàn)我們已經(jīng)基本了解了寒锚,但還是不知道為啥能夠在容器遍歷的時候?qū)ζ溥M行修改而不拋出異常。所以,來看一下他的迭代器吧:
到這里刹前,我們應(yīng)該就可以想明白了泳赋!CopyOnWriteArrayList在使用迭代器遍歷的時候,操作的都是原數(shù)組喇喉!
2.1.4CopyOnWriteArrayList缺點
看了上面的實現(xiàn)源碼祖今,我們應(yīng)該也大概能分析出CopyOnWriteArrayList的缺點了。
內(nèi)存占用:如果CopyOnWriteArrayList經(jīng)常要增刪改里面的數(shù)據(jù)拣技,經(jīng)常要執(zhí)行add()衅鹿、set()、remove()的話过咬,那是比較耗費內(nèi)存的。
因為我們知道每次add()制妄、set()掸绞、remove()這些增刪改操作都要復(fù)制一個數(shù)組出來。
數(shù)據(jù)一致性:CopyOnWrite容器只能保證數(shù)據(jù)的最終一致性耕捞,不能保證數(shù)據(jù)的實時一致性衔掸。
從上面的例子也可以看出來,比如線程A在迭代CopyOnWriteArrayList容器的數(shù)據(jù)俺抽。線程B在線程A迭代的間隙中將CopyOnWriteArrayList部分的數(shù)據(jù)修改了(已經(jīng)調(diào)用setArray()了)敞映。但是線程A迭代出來的是原有的數(shù)據(jù)。
2.1.5CopyOnWriteSet
CopyOnWriteArraySet的原理就是CopyOnWriteArrayList磷斧。
ConcurrentHashMap
數(shù)據(jù)結(jié)構(gòu)
JDK 1.8中ConcurrentHashMap拋棄了分段鎖技術(shù)的實現(xiàn)振愿,直接采用CAS + synchronized保證并發(fā)更新的安全性,底層采用數(shù)組+鏈表+紅黑樹的存儲結(jié)構(gòu)弛饭。其包含核心靜態(tài)內(nèi)部類 Node冕末。
首先通過一張圖來看下數(shù)據(jù)結(jié)構(gòu)吧:
數(shù)據(jù)結(jié)構(gòu)圖
說明:數(shù)據(jù)結(jié)構(gòu)采用數(shù)組 + 鏈表 + 紅黑樹的方式實現(xiàn)。當(dāng)鏈表中(bucket)的節(jié)點個數(shù)超過8個時侣颂,會轉(zhuǎn)換成紅黑樹的數(shù)據(jù)結(jié)構(gòu)存儲档桃,這樣設(shè)計的目的是為了減少同一個鏈表沖突過大情況下的讀取效率。
Java8中主要做了如下優(yōu)化:
1.將Segment拋棄掉了憔晒,直接采用Node(繼承自Map.Entry)作為table元素藻肄。
2.修改時,不再采用ReentrantLock加鎖拒担,直接用內(nèi)置synchronized加鎖嘹屯,java8的內(nèi)置鎖比之前版本優(yōu)化了很多,相較ReentrantLock澎蛛,性能不并差抚垄。
3.size方法優(yōu)化,增加了CounterCell內(nèi)部類,用于并行計算每個bucket的元素數(shù)量呆馁。
內(nèi)部類和繼承關(guān)系
Java8中ConcurrentHashMap增加了很多內(nèi)部類來支持一些操作和優(yōu)化性能桐经。下面介紹幾個核心的內(nèi)部類。
ConcurrentHashMap幾個核心內(nèi)部類關(guān)系圖
(1)Node類:存放元素的key,value,hash值,next下一個鏈表節(jié)點的引用浙滤。用于bucket為鏈表時阴挣。
(2)TreeBin:內(nèi)部屬性有root,first節(jié)點纺腊,以及root節(jié)點的鎖狀態(tài)變量lockState畔咧,這是一個讀寫鎖的狀態(tài)。用于存放紅黑樹的root節(jié)點揖膜,并用讀寫鎖lockState控制在寫操作即將要調(diào)整樹結(jié)構(gòu)前誓沸,先讓讀線程完成讀操作。從鏈表結(jié)構(gòu)調(diào)整為紅黑樹時壹粟,table中索引下標(biāo)存儲的即為TreeBin拜隧。
(3)TreeNode:紅黑樹的節(jié)點,存放了父節(jié)點趁仙,左子節(jié)點洪添,右子節(jié)點的引用,以及紅黑節(jié)點標(biāo)識雀费。
(4)ForwardingNode:在調(diào)用transfer()方法期間干奢,插入bucket頭部的節(jié)點,主要用來標(biāo)識在擴容時元素的移動狀態(tài)盏袄,即是否在擴容時還有并發(fā)的插入節(jié)點忿峻,并保證該節(jié)點也能夠移動到擴容后的表中。
(5)ReservationNode:占位節(jié)點辕羽,不存儲任何信息炭菌,無實際用處,僅用于computeIfAbsent和compute方法中逛漫。
重要屬性介紹
以上的一些屬性黑低,在初始化,擴容酌毡,鏈表轉(zhuǎn)紅黑樹等方法中用到克握。屬性眾多,sizeCtl枷踏,counterCells都比較重要菩暗。
sizeCtl:即作為table初始化狀態(tài)的標(biāo)識,也用作擴容時的線程數(shù)標(biāo)識旭蠕,還用作初始和擴容后table的容量標(biāo)識停团,用處很多旷坦,不同狀態(tài)值代表的含義如下:
1、 -1:標(biāo)識table正在初始化
2佑稠、- N:標(biāo)識table正在進行擴容秒梅,并且有N - 1個線程一起在進行擴容
3、正數(shù):初始化table的大小舌胶,如果值大于初始容量大小捆蜀,則表示擴容后的table大小。
鏈接:https://juejin.im/post/5be23e6ef265da6135720d61 鏈接:http://www.reibang.com/p/85d158455861