ConcurrentHashMap是線程安全的容器。
ConcurrentHashMap是基于樂觀鎖實現(xiàn)線程安全的。
ConcurrentHashMap是基于CAS算法和volatile實現(xiàn)線程安全的。
java1.8版本對ConcurrentHashMap進行了較大的優(yōu)化,我們先看1.8之前的ConcurrentHashMap,然后文章的后半部分再來研究1.8版本的ConcurrentHashMap。
我們都知道,ConcurrentHashMap使用了分段map的思想,也就是segment,segment是如何提高并發(fā)性的?又是如何保證線程安全的?
1,分段鎖技術
什么是segment呢,ConcurrentHashMap會把數(shù)據(jù)分段存儲,然后每段數(shù)據(jù)分別加鎖??丛创a可以發(fā)現(xiàn),其實就是遍歷node的時候,加上synchronized關鍵字,而Hashtable是在整個put方法上加上synchronized關鍵字。
數(shù)據(jù)分段加鎖以后,由于一個線程只鎖定了某一段數(shù)據(jù),所以其他線程還可以訪問其他數(shù)據(jù)。這樣就達到了提高并發(fā)能力的要求。
為了理解更加深入,我們考慮一下下面這種場景,如果兩個線程要獲取map中的同一個key,是否能同時訪問呢?也就是說,這種情況下能并發(fā)執(zhí)行嗎?segment到底是如何分段的?如果我的map中只有2個兼職對,難道也要分成16個segment嗎?
從代碼的角度來說,Segment是ConcurrentHashMap的一個內部類,繼承了重入鎖ReentrantLock,實現(xiàn)了序列化接口。
2,put方法
先通過segmentFor(hash)定位到數(shù)據(jù)所在的分段,然后調用分段的put方法。put方法的實現(xiàn)使用的是加鎖操作,這里加的是重入鎖,也就是說put方法沒有使用CAS樂觀鎖,而只是使用了segment分段處理。java1.8開始使用CAS樂觀鎖代替RentrantLock
3,get方法
4,remove方法
先根據(jù)key定位segment,然后調用segment的containKey(key, hash, null)方法。
remove方法的實現(xiàn)使用的是加鎖操作,這里加的是重入鎖,也就是說remove方法沒有使用CAS樂觀鎖,而只是使用了segment分段處理。
5,size方法
size方法統(tǒng)計的是整個map的大小,所以必須要統(tǒng)計所有的segment大小,然后求和。Segment里面有一個count變量,這個變量是volatile類型的,也就是內存可見的,但是在多線程環(huán)境下,不是線程安全的。因為你拿到count以后還要求和,雖然拿到的那一刻,是線程安全的,但是還要求和,求和操作的過程中如果count改變了,最后統(tǒng)計的結果就不準確了。
為了解決這個問題,ConcurrentHashMap提供了這樣一種思路:累加過程中,累加結果發(fā)生改變的概率很小,所以
ConcurrentHashMap的做法是先在segment不加鎖的情況下,進行兩次累加操作,如果累加結果一致,OK,直接把累加結果當做size返回。如果累加結果不一致,則把所有segment的put方法、remove方法、clear方法鎖住,然后在獲取count進行累加操作。
6,containKey方法
先根據(jù)key定位segment,然后調用segment的containKey(key, hash)方法。
這個方法不存在線程安全的問題。源代碼如下:

二,java1.8 ConcurrentHashMap源碼解析
1,put方法
put方法
1,如果Node數(shù)組為空,則初始化,也就是說map中Node的初始化是延遲的,知道put操作的時候,才真正初始化。
2,spread(key.hashcode()),二次hash,減少hash碰撞。
3,如果桶的深度大于1,需要追加到鏈表或者紅黑樹時,使用synchronized進行同步。ConcurrentHashMap主要是這個操作使用了synchronized進行同步,其他都是使用RentrantLock。
三,Java1.8對ConcurrentHashMap的優(yōu)化
1,使用CAS樂觀鎖和volatile代替RentrantLock
2,spread二次哈希進行segment分段。
3,stream提高并行處理能力。