作者: 一字馬胡
轉(zhuǎn)載標(biāo)志 【2017-11-03】
更新日志
日期 | 更新內(nèi)容 | 備注 |
---|---|---|
2017-11-03 | 添加轉(zhuǎn)載標(biāo)志 | 持續(xù)更新 |
導(dǎo)入
ConcurrentHashMap是HashMap的線程安全版本的實現(xiàn)版本沟涨,關(guān)于HashMap的分析總結(jié)藏姐,可以參考文章Java 8 HashMap源碼分析名党。本文將基于java 8中的Java 8 實現(xiàn)來分析ConcurrentHashMap,與之前版本的ConcurrentHashMap實現(xiàn)來看趁仙,java 8中做了較大調(diào)整,本文僅分析java 8的實現(xiàn)随橘,java 8 之前的實現(xiàn)暫不做分析还栓。為了更好的導(dǎo)入本文,首先展示一下ConcurrentHashMap的結(jié)構(gòu)敬锐,請看下面的圖片:
和HashMap類似辞嗡,ConcurrentHashMap使用了一個table來存儲Node捆等,ConcurrentHashMap同樣使用記錄的key的hashCode來尋找記錄的存儲index,而處理哈希沖突的方式與HashMap也是類似的续室,沖突的記錄將被存儲在同一個位置上,形成一條鏈表谒养,當(dāng)鏈表的長度大于8的時候會將鏈表轉(zhuǎn)化為一棵紅黑樹挺狰,從而將查找的復(fù)雜度從O(N)降到了O(lgN)。下文中將詳細(xì)分析ConcurrentHashMap的實現(xiàn)买窟,以及ConcurrentHashMap是如何保證在并發(fā)環(huán)境下的線程安全的丰泊。
ConcurrentHashMap詳解
哈希桶Table初始化
首先來看一下table是如何初始化的,初始化table的工作將發(fā)生在進(jìn)行put操作時始绍,如果發(fā)現(xiàn)table還沒有被初始化瞳购,那么就會調(diào)用方法initTable來進(jìn)行table的初始化,下面展示了初始化table的具體流程代碼:
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
這里面有一個比較關(guān)鍵的地方亏推,就是sizeCtl這個變量学赛,下面是它的定義:
/**
* Table initialization and resizing control. When negative, the
* table is being initialized or resized: -1 for initialization,
* else -(1 + the number of active resizing threads). Otherwise,
* when table is null, holds the initial table size to use upon
* creation, or 0 for default. After initialization, holds the
* next element count value upon which to resize the table.
*/
private transient volatile int sizeCtl;
sizeCtl是一個用于同步多個線程的共享變量,如果它的當(dāng)前值為負(fù)數(shù)吞杭,則說明table正在被某個線程初始化或者擴(kuò)容盏浇,所以,如果某個線程想要初始化table或者對table擴(kuò)容芽狗,需要去競爭sizeCtl這個共享變量绢掰,獲得變量的線程才有許可去進(jìn)行接下來的操作,沒能獲得的線程將會一直自旋來嘗試獲得這個共享變量童擎,所以獲得sizeCtl這個變量的線程在完成工作之后需要設(shè)置回來滴劲,使得其他的線程可以走出自旋進(jìn)行接下來的操作。而在initTable方法中我們可以看到顾复,當(dāng)線程發(fā)現(xiàn)sizeCtl小于0的時候班挖,他就會讓出CPU時間,而稍后再進(jìn)行嘗試捕透,當(dāng)發(fā)現(xiàn)sizeCtl不再小于0的時候聪姿,就會通過調(diào)用方法compareAndSwapInt來講sizeCtl共享變量變?yōu)?1,以告訴其他試圖獲得sizeCtl變量的線程乙嘀,目前正在由本線程在享用該變量末购,在我完成我的任務(wù)之前你可以先休息一會,等會再來試試吧虎谢,我完成工作之后會釋放掉的盟榴。而其他的線程在發(fā)現(xiàn)sizeCtl小于0的時候就會理解這種交流,他們會讓出cpu時間婴噩,等待下次調(diào)度再來嘗試獲取sizeCtl來進(jìn)行自己的工作擎场。在完成初始化table的任務(wù)之后羽德,線程需要將sizeCtl設(shè)置成可以使得其他線程獲得變量的狀態(tài),這其中還有一個地方需要注意迅办,就是在某個線程通過U.compareAndSwapInt方法設(shè)置了sizeCtl之前和之后進(jìn)行了兩次check宅静,來檢測table是否被初始化過了,這種檢測是必須的站欺,因為在并發(fā)環(huán)境下姨夹,可能前一個線程正在初始化table但是還沒有成功初始化,也就是table依然還為null矾策,而有一個線程發(fā)現(xiàn)table為null他就會進(jìn)行競爭sizeCtl以進(jìn)行table初始化磷账,但是當(dāng)前線程在完成初始化之后,那個試圖初始化table的線程獲得了sizeCtl贾虽,但是此時table已經(jīng)被初始化了逃糟,所以,如果沒有再次判斷的話蓬豁,可能會將之后進(jìn)行put操作的線程的更新覆蓋掉绰咽,這是極為不安全的行為。
ConcurrentHashMap查詢記錄方法詳解
上面分析了ConcurrentHashMap的table初始化庆尘,現(xiàn)在來看一下ConcurrentHashMap的查詢操作的實現(xiàn)細(xì)節(jié)剃诅,在ConcurrentHashMap中查詢一條記錄首先需要知道這條記錄存儲的table的位置(可以成為卡槽,每個卡槽中都會有一個鏈表或者一棵紅黑樹)驶忌,該位置上可能為null矛辕,如果為null,說明想要查詢的記錄還不存在于ConcurrentHashMap中付魔,否則聊品,就在該位置上的鏈表或者紅黑樹中查找記錄,下面來詳細(xì)分析一下ConcurrentHashMap的get方法的實現(xiàn)細(xì)節(jié):
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
首先几苍,計算出記錄的key的hashCode翻屈,然后通過使用(hashCode & (length - 1))的計算方法來獲得該記錄在table中的index,然后判斷該位置上是否為null妻坝,如果為null伸眶,則返回null,否則刽宪,如果該位置上的第一個元素(鏈表頭節(jié)點或者紅黑樹的根節(jié)點)與我們先要查找的記錄匹配厘贼,則直接返回這個節(jié)點的值,否則圣拄,如果該節(jié)點的hashCode小于0嘴秸,則說明該位置上是一顆紅黑樹,至于為什么hashCode值小于0就代表是一顆紅黑樹而不是鏈表了,這就要看下面的代碼了:
static final int TREEBIN = -2; // hash for roots of trees
TreeBin(TreeNode<K,V> b) {
super(TREEBIN, null, null, null);
......
}
而TREEBIN的值為-2岳掐,也就是小于0成立凭疮,根據(jù)他的說明,TREEBIN想要代表的是一顆紅黑樹的根節(jié)點串述,所以在判斷到table的某個位置上的第一個節(jié)點的hashCode值小于0的時候执解,就可以判斷為該位置上是一棵紅黑樹,繼續(xù)回到get方法纲酗,如果是紅黑樹材鹦,則通過調(diào)用Node的find方法來查找到節(jié)點,而這個Node的find方法在子類中被重寫耕姊,所以會直接調(diào)用子類的find方法來進(jìn)行查找。還有一種情況是table的index位置上為一條鏈表栅葡,那么就通過鏈表的查找方法來進(jìn)行記錄查找茉兰。最后需要注意的是,ConcurrentHashMap是一種線程安全的HashMap欣簇,但是我們沒有發(fā)現(xiàn)在get方法的過程中使用任何與鎖等效的組件來做線程同步规脸,為什么呢?對于讀來說熊咽,允許多個線程一起讀是很正常的莫鸭,而且在Node的實現(xiàn)上,ConcurrentHashMap做了一些手腳:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
....
}
/**
* The array of bins. Lazily initialized upon first insertion.
* Size is always a power of two. Accessed directly by iterators.
*/
transient volatile Node<K,V>[] table;
我們發(fā)現(xiàn)table數(shù)組是被volatile關(guān)鍵字修飾的横殴,這就代表我們不需要擔(dān)心table數(shù)組的線程可見性問題被因,也就沒有必要再加鎖來實現(xiàn)并發(fā)了。
ConcurrentHashMap插入記錄方法詳解
上文中分析了ConcurrentHashMap的查詢方法衫仑,下面來分析一下ConcurrentHashMap的插入操作時如何完成的梨与。需要注意的一點是,在進(jìn)行put操作的時候文狱,我們可能會發(fā)現(xiàn)table數(shù)組還沒有初始化的情況粥鞋,或者發(fā)現(xiàn)table中容納的記錄數(shù)量超過了閾值的情況,前者我們需要進(jìn)行table的初始化瞄崇,而后者需要我們對table進(jìn)行擴(kuò)容操作呻粹。初始化table的過程我們在上文中已經(jīng)進(jìn)行了分析,下面只分析table的擴(kuò)容操作苏研。首先來考慮put一個記錄需要的過程等浊,第一,我們需要計算這個記錄的key的hashCode楣富,并且根據(jù)hashCode來計算它在table數(shù)組中應(yīng)該存儲的index凿掂,然后將他存放到對應(yīng)位置里面的鏈表或者紅黑樹中去,并且在某些情況下要進(jìn)行鏈表轉(zhuǎn)換紅黑樹的操作,以及table擴(kuò)容操作等庄萎。還有一件重要的事情就是變更table的size踪少,這一點在后文中還要專門分析到。下面首先展示了put操作涉及的流程:
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
首先糠涛,計算記錄的key的hashCode援奢,然后計算table的index位置,然后獲取該index的值忍捡,如果該位置還為null集漾,說明該位置上還沒有記錄,則通過調(diào)用casTabAt方法來講該新的記錄插入到table的index位置上去砸脊,否則具篇,通過synchronized關(guān)鍵字對table的index位置加鎖,需要注意的是凌埂,當(dāng)前線程只會鎖住table的index位置驱显,其他位置上沒有鎖住,所以此時其他線程可以安全的獲得其他的table位置來進(jìn)行操作瞳抓。這也就提高了ConcurrentHashMap的并發(fā)度埃疫。然后判斷table的index位置上的第一個節(jié)點的hashCode值,這個節(jié)點要么是鏈表的頭節(jié)點孩哑,要么是紅黑樹的根節(jié)點栓霜,如果hashCode值小于0,那么就是一顆紅黑樹横蜒,至于為什么是這樣胳蛮,上文中已經(jīng)提到,如果不小于0愁铺,那么就還是一條鏈表鹰霍,如果是一條鏈表,那么就尋找是否已經(jīng)有記錄的key和當(dāng)前想要插入的記錄是一致的茵乱,如果一致茂洒,那么這次put的效果就是replace,否則瓶竭,將該記錄添加到鏈表中去督勺。如果是一顆紅黑樹,那么就通過調(diào)用putTreeVal方法來進(jìn)行插入操作斤贰。在插入操作完成之后智哀,需要判斷本次操作是否是更新操作,如果是更新操作荧恍,則不會造成size的變化瓷叫,否則屯吊,如果本次put操作時一次添加操作,那么就需要進(jìn)行更新size的操作摹菠,而size的更新涉及到并發(fā)環(huán)境盒卸,所以較為復(fù)雜,并且table的擴(kuò)容操作也會在更新size的時候發(fā)生次氨,如果在更新size之后發(fā)現(xiàn)table中的記錄數(shù)量達(dá)到了閾值蔽介,就需要進(jìn)行擴(kuò)容操作,這也是較為復(fù)雜的一步煮寡。還有一點需要說明的是虹蓄,ConcurrentHashMap和HashMap的區(qū)別還有一點,就是HashMap允許一個key和value為null幸撕,而ConcurrentHashMap則不允許key和value為null薇组,如果發(fā)現(xiàn)key或者value為null,則會拋出NPE坐儿,這一點需要特別注意体箕,而這也說明,在ConcurrentHashMap中可以通過使用get操作來測試是否具有某個記錄挑童,因為只要get方法返回null,就說明table中必然不存在一個記錄和當(dāng)前查詢的匹配跃须,而在HashMap中站叼,get操作返回null有可能是我們查詢的記錄的value就是null,所以不能使用get方法來測試某個記錄是否存在于table中菇民。
ConcurrentHashMap記錄數(shù)量更新
上面分析put操作的時候提到尽楔,在完成一次put操作之后,需要更新table中的記錄數(shù)量第练,并且在更新之后如果發(fā)現(xiàn)超出了閾值阔馋,那么就需要進(jìn)行table擴(kuò)容操作,下面來具體分析一下這一過程的前后文娇掏。更新記錄數(shù)量的操作通過調(diào)用方法addCount來完成呕寝,下面是該方法的細(xì)節(jié):
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
ConcurrentHashMap維護(hù)baseCount來表示當(dāng)前的記錄數(shù)量,這在后面獲取記錄數(shù)量的size方法中會用到婴梧,而在put操作和remove操作的時候回通過調(diào)用方法addCount來更新它下梢。如果CounterCell數(shù)組為空,則通過調(diào)用方法fullAddCount來初始化數(shù)組counterCells塞蹭,因為本部分內(nèi)容過于復(fù)雜孽江,目前不適合分析,點到為止番电。在更新table中記錄數(shù)量的時候岗屏,還要考慮一種情況,記錄的數(shù)量達(dá)到了閾值,那么就需要進(jìn)行擴(kuò)容操作这刷,這部分的代碼也過于復(fù)雜婉烟,并且ConcurrentHashMap的擴(kuò)容操作的條件貌似和HashMap是不一樣的,它的說法是“如果table過小崭歧,并且沒有被擴(kuò)容隅很,那么就需要進(jìn)行擴(kuò)容”,擴(kuò)容需要使用transfer方法來將久的記錄遷移到新的table中去率碾。目前叔营,我們需要了解的是,ConcurrentHashMap會在我們進(jìn)行更新table的記錄數(shù)量的時候可能進(jìn)行擴(kuò)容操作所宰,而前提是“table過小绒尊,并且還沒有被擴(kuò)容”,這部分的代碼將在未來某個適宜的時刻在進(jìn)行分析總結(jié)仔粥。
ConcurrentHashMap移除記錄操作
現(xiàn)在來分析一下ConcurrentHashMap是如何進(jìn)行記錄的移除操作的婴谱。下面首先展示了remove方法的調(diào)用代碼:
public V remove(Object key) {
return replaceNode(key, null, null);
}
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<K,V> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
else if (f instanceof TreeBin) {
validated = true;
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> r, p;
if ((r = t.root) != null &&
(p = r.findTreeNode(hash, key, null)) != null) {
V pv = p.val;
if (cv == null || cv == pv ||
(pv != null && cv.equals(pv))) {
oldVal = pv;
if (value != null)
p.val = value;
else if (t.removeTreeNode(p))
setTabAt(tab, i, untreeify(t.first));
}
}
}
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
刪除操作屬于寫類型的操作,所以在進(jìn)行刪除的時候需要對table中的index位置加鎖躯泰,ConcurrentHashMap使用synchronized關(guān)鍵字將table中的index位置鎖住谭羔,然后進(jìn)行刪除伯顶,remove方法調(diào)用了replaceNode方法來進(jìn)行實際的操作崇呵,而刪除操作的步驟首先依然是計算記錄的hashCode,然后根據(jù)hashCode來計算table中的index值丘喻,然后根據(jù)table中的index位置上是一條鏈表還是一棵紅黑樹來使用不同的方法來刪除這個記錄诵竭,刪除記錄的操作需要進(jìn)行記錄數(shù)量的更新(調(diào)用addCount方法進(jìn)行)话告。
ConcurrentHashMap的size方法詳解
最后,來分析一下ConcurrentHashMap是怎么獲得table中的記錄數(shù)量的卵慰,ConcurrentHashMap通過size方法來獲得記錄數(shù)量沙郭,下面展示了size方法的細(xì)節(jié):
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
ConcurrentHashMap的記錄數(shù)量需要結(jié)合baseCount和counterCells數(shù)組來得到,通過累計兩者的數(shù)量即可獲得當(dāng)前ConcurrentHashMap中的記錄總量裳朋。
java7中的ConcurrentHashMap實現(xiàn)
上文中分析了java8中的ConcurrentHashMap實現(xiàn)病线,現(xiàn)在來分析一下java7中的實現(xiàn)。java7中的實現(xiàn)在底層使用了數(shù)組+鏈表的方法鲤嫡,下面展示了java7中ConcurrentHashMap的數(shù)據(jù)結(jié)構(gòu):
整個結(jié)構(gòu)是一個segment數(shù)組氧苍,segment數(shù)組的大小決定了ConcurrentHashMap的并發(fā)度,默認(rèn)是16泛范,為什么這么說呢让虐?是因為在java7的ConcurrentHashMap實現(xiàn)上,使用了所謂分段鎖的方法罢荡,而所謂分段鎖就是將記錄分段存儲赡突,不同段的訪問互相不影響对扶,某個線程想要訪問某一個段的時候就需要對該段上鎖,而其他線程不能訪問在有其他線程加鎖的段惭缰,這和對整體加鎖的方法相比是一種偉大的進(jìn)步浪南,對于java7中的具體操作分析就不在此進(jìn)行了,如有需要漱受,就在其他的文章中再進(jìn)行分析總結(jié)络凿。
本文根據(jù)jdk 8 中的源碼來分析了ConcurrentHashMap的實現(xiàn)細(xì)節(jié),但是因為ConcurrentHashMap的實現(xiàn)過于復(fù)雜昂羡,本文僅分析了非常表面的內(nèi)容絮记,分析ConcurrentHashMap的源碼的意義在于可以明白為什么ConcurrentHashMap是線程安全的,以及為實現(xiàn)線程安全虐先,ConcurrentHashMap相比于HashMap增加了哪些內(nèi)容怨愤,本文通過分析ConcurrentHashMap的實現(xiàn),比較HashMap的實現(xiàn)蛹批,可以明顯的發(fā)現(xiàn)它們的實現(xiàn)復(fù)雜度上是不在一個等級上的撰洗,HashMap更多的是數(shù)據(jù)結(jié)構(gòu)上的玩弄,而ConcurrentHashMap則更多的需要考慮如何高效的實現(xiàn)并發(fā)環(huán)境下的線程安全腐芍,簡單來說差导,ConcurrentHashMap不僅實現(xiàn)了HashMap支持的所有功能,并且保持了和HashMap一樣的高效的前提下猪勇,還實現(xiàn)了線程安全柿汛,分析源碼可以發(fā)現(xiàn)ConcurrentHashMap是基于CAS來實現(xiàn)線程安全的,CAS是一種輕量級的鎖埠对,它不會阻塞線程,而是會等待直到獲得變量裁替,然后進(jìn)行業(yè)務(wù)操作项玛,這和鎖需要阻塞線程來實現(xiàn)線程安全來比較,是一種很大的改良弱判,因為基于鎖的同步控制需要讓線程獲得鎖襟沮,而獲得鎖之前是要阻塞等待的,它需要另外的線程來喚醒它讓他再次去競爭鎖昌腰,關(guān)于這部分的內(nèi)容可以參考文章Java同步框架AbstractQueuedSynchronizer开伏,AQS是java中實現(xiàn)鎖的底層支持,也是為程序員實現(xiàn)線程同步的基礎(chǔ)框架遭商,基于AQS實現(xiàn)自己的線程同步器十分簡便固灵。介于ConcurrentHashMap的復(fù)雜性,本文就當(dāng)做是對ConcurrentHashMap分析的開篇吧劫流,未來會不定時對ConcurrentHashMap的分析總結(jié)進(jìn)行補(bǔ)充巫玻。