JDK1.7
首先將數(shù)據(jù)分為一段一段的存儲(chǔ),然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問(wèn)其中一個(gè)段數(shù)據(jù)時(shí),其他段的數(shù)據(jù)也能被其他線程訪問(wèn)。
在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式進(jìn)行實(shí)現(xiàn),結(jié)構(gòu)如下:
一個(gè) ConcurrentHashMap 里包含一個(gè) Segment 數(shù)組。Segment 的結(jié)構(gòu)和HashMap類似,是一種數(shù)組和鏈表結(jié)構(gòu),一個(gè) Segment 包含一個(gè) HashEntry 數(shù)組,每個(gè) HashEntry 是一個(gè)鏈表結(jié)構(gòu)的元素,每個(gè) Segment 守護(hù)著一個(gè)HashEntry數(shù)組里的元素,當(dāng)對(duì) HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得對(duì)應(yīng)的 Segment的鎖。
1. 該類包含兩個(gè)靜態(tài)內(nèi)部類 HashEntry 和 Segment ;前者用來(lái)封裝映射表的鍵值對(duì),后者用來(lái)充當(dāng)鎖的角色;
2. Segment 是一種可重入的鎖 ReentrantLock,每個(gè) Segment 守護(hù)一個(gè)HashEntry 數(shù)組里得元素,當(dāng)對(duì) HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得對(duì)應(yīng)的 Segment 鎖。
JDK1.8
在JDK1.8中,放棄了Segment臃腫的設(shè)計(jì),取而代之的是采用Node + CAS + Synchronized來(lái)保證并發(fā)安全進(jìn)行實(shí)現(xiàn),synchronized只鎖定當(dāng)前鏈表或紅黑二叉樹(shù)的首節(jié)點(diǎn),這樣只要hash不沖突,就不會(huì)產(chǎn)生并發(fā),效率又提升N倍。
結(jié)構(gòu)如下:
附加源碼,有需要的可以看看
插入元素過(guò)程(建議去看看源碼):
如果相應(yīng)位置的Node還沒(méi)有初始化,則調(diào)用CAS插入相應(yīng)的數(shù)據(jù);
如果相應(yīng)位置的Node不為空,且當(dāng)前該節(jié)點(diǎn)不處于移動(dòng)狀態(tài),則對(duì)該節(jié)點(diǎn)加synchronized鎖,如果該節(jié)點(diǎn)的hash不小于0,則遍歷鏈表更新節(jié)點(diǎn)或插入新節(jié)點(diǎn);
1. 如果該節(jié)點(diǎn)是TreeBin類型的節(jié)點(diǎn),說(shuō)明是紅黑樹(shù)結(jié)構(gòu),則通過(guò)putTreeVal方法往紅黑樹(shù)中插入節(jié)點(diǎn);如果binCount不為0,說(shuō)明put操作對(duì)數(shù)據(jù)產(chǎn)生了影響,如果當(dāng)前鏈表的個(gè)數(shù)達(dá)到8個(gè),則通過(guò)treeifyBin方法轉(zhuǎn)化為紅黑樹(shù),如果oldVal不為空,說(shuō)明是一次更新操作,沒(méi)有對(duì)元素個(gè)數(shù)產(chǎn)生影響,則直接返回舊值;
2. 如果插入的是一個(gè)新節(jié)點(diǎn),則執(zhí)行addCount()方法嘗試更新元素個(gè)數(shù)baseCount;