ConcurrentHashMap 原始碼分析
和HashMap不同的是,ConcurrentHashMap採用分段加鎖的方式保障執行緒安全,JDK 1.8之後,ConcurrentHashMap的底層資料結構從1.8開始跟HashMap差不多。
HashTable也是執行緒安全的,儲存Key-Value鍵值對的資料結構,Key和Value都不能為空,但不推薦使用,因為其所有的方法採用synchronized修飾,效率低。
Key和Value都不能為Null的原因是:如果map.get(key)返回null,可以認為是value的值本來就是null,也可以認為map中不存在key的儲存資料,因此具有二義性,但HashMap在單執行緒環境,可以透過map.containsKey(key)判斷,消除而已性。
但在多執行緒環境中,map.get(key)和map.containsKey(key)是非原子的操作,可能線上程A的兩個語句執行之間,其他執行緒B執行map.put(key,value),導致執行緒A無法消除上面的二義性。
下圖是ConcurrentHashMap的UML關係圖。
image-20200809215755661
1、底層儲存結構
1.1、JDK 1.7的儲存結構,瞭解即可
在JDK 1.7,ConcurrentHashMap透過對Segment的分段加鎖實現執行緒安全。一個Segment裡面就是HashMap的儲存結構,可以擴容。Segment的資料量初始化以後不可以更改,預設值16,因此預設支援16個執行緒同時操作ConcurrentHashMap。
img
1.2 JDK 1.8的儲存結構
JDK 1.8之後,儲存結構變化比較大,跟HashMap類似。紅黑樹節點小於某個數(預設值6)又會轉換為連結串列。
image-20200809221531416
[]ConcurrentHashMap的主要成員變數,類似HashMap,補上註釋
2、ConcurrentHashMap的構造方法
ConcurrentHashMap的預設構造容量為16,在初始化的時候並不會初始化table陣列。同HashMap一樣,在put第一個元素的時候才會initTable()初始化陣列。
/**Creates a new,empty map with the default initial table size(16).*/
public ConcurrentHashMap(){
}
//設定初始化大小的建構函式
public ConcurrentHashMap(int initialCapacity){
this(initialCapacity,LOAD_FACTOR,1);
}
//根據傳入的map初始化
public ConcurrentHashMap(Map<?extends K,?extends V>m){
this.sizeCtl=DEFAULT_CAPACITY;
putAll(m);
}
//設定初始容量和載入因子的大小
public ConcurrentHashMap(int initialCapacity,float loadFactor){
this(initialCapacity,loadFactor,1);
}
//初始容量、載入因子、併發級別
public ConcurrentHashMap(int initialCapacity,
float loadFactor,int concurrencyLevel){
//資料校驗
if(!(loadFactor>0.0f)||initialCapacity<0||concurrencyLevel<=0)
throw new IllegalArgumentException();
//如果初始容量小於併發級別
if(initialCapacity<concurrencyLevel)//Use at least as many bins
initialCapacity=concurrencyLevel;//as estimated threads
//一些比較
long size=(long)(1.0+(long)initialCapacity/loadFactor);
int cap=(size>=(long)MAXIMUM_CAPACITY)?
MAXIMUM_CAPACITY:tableSizeFor((int)size);
this.sizeCtl=cap;
}
3、get、put方法
3.1 get方法,根據key找value,沒有返回null
get的流程總體和HashMap差不多,只不過是透過頭結點的hash值判斷是紅黑樹還是連結串列。
static final int MOVED=-1;//轉發節點?TODO作用?
static final int TREEBIN=-2;//跟節點
static final int RESERVED=-3;//臨時保留的節點?TODO作用?
static final int HASH_BITS=0x7fffffff;//hash的擾動函式spread()計算用的
//根據key獲取value值
public V get(Object key){
Node<K,V>[]tab;Node<K,V>e,p;int n,eh;K ek;
//計算hash值
int h=spread(key.hashCode());
//集散所在的hash桶
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)
//頭結點hash值小於0,說明正在擴容或者是紅黑樹,find查詢
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;
}
3.2、put方法
put方法的流程跟HashMap的流程差不多,不同點在於執行緒安全,自旋,CAS,synchronized
onlyIfAbsent如果為true,如果已經存在了key,不會替換舊的值。
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){
//key和value都不能為null
if(key==null||value==null)throw new NullPointerException();
//計算hash(key)的擾動函式
int hash=spread(key.hashCode());
//離岸邊的長度
int binCount=0;
for(Node<K,V>[]tab=table;;){
Node<K,V>f;int n,i,fh;K fk;V fv;
//如果table還沒有初始化,就初始化table(自旋+CAS)
if(tab==null||(n=tab.length)==0)
tab=initTable();
else if((f=tabAt(tab,i=(n-1)&hash))==null){
//如果當前hash桶為null,直接放入,CAS加入,成功了就直接break
if(casTabAt(tab,i,null,new Node<K,V>(hash,key,value)))
break;//no lock when adding to empty bin
}
//TODO:
else if((fh=f.hash)==MOVED)
tab=helpTransfer(tab,f);
else if(onlyIfAbsent//check first node without acquiring lock
&&fh==hash
&&((fk=f.key)==key||(fk!=null&&key.equals(fk)))
&&(fv=f.val)!=null)
return fv;
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;
//如果存在hash(key)和key對應的節點,直接更改value值
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);
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;
}
}
else if(f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if(binCount!=0){
//是否要轉為紅黑樹
if(binCount>=TREEIFY_THRESHOLD)
treeifyBin(tab,i);
//舊的值
if(oldVal!=null)
return oldVal;
break;
}
}
}
addCount(1L,binCount);
return null;
}
4、TODO ConcurrentHashMap的擴容方法
ConcurrentHashMap也是預設擴容2倍,擴容的方法transfer()
Node<K,V>[]nt=(Node<K,V>[])new Node<?,?>[n<<1];
5、總結
ConcurrentHashMap在JDK 1.7和1.8變化很大,在JDK 1.7中,採用Segment分段儲存資料,也透過Segment分段加鎖。
而在JDK 1.8中,使用synchronized鎖定hash桶的連結串列的首節點/紅黑樹的根節點,只要hash(key)不衝突,就不會影響其他執行緒。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69995861/viewspace-2761551/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- ConcurrentHashMap原始碼分析-JDK18HashMap原始碼JDK
- ConcurrentHashMap 實現原理和原始碼分析HashMap原始碼
- hashmap和concurrenthashmap原始碼分析(1.7/1.8)HashMap原始碼
- 原始碼分析–ConcurrentHashMap與HashTable(JDK1.8)原始碼HashMapJDK
- ConcurrentHashMap原始碼解析HashMap原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)JavaHashMap原始碼
- ConcurrentHashMap 原始碼分析03之內部類ReduceTaskHashMap原始碼
- ConcurrentHashMap執行緒安全機制以及原始碼分析HashMap執行緒原始碼
- ConcurrentHashMap原始碼解讀HashMap原始碼
- ConcurrentHashMap原始碼閱讀HashMap原始碼
- ConcurrentHashMap原始碼解讀一HashMap原始碼
- ConcurrentHashMap原始碼解讀二HashMap原始碼
- 併發程式設計之 ConcurrentHashMap(JDK 1.8) putVal 原始碼分析程式設計HashMapJDK原始碼
- 還不懂 ConcurrentHashMap ?這份原始碼分析瞭解一下HashMap原始碼
- 從原始碼分析ConcurrentHashMap執行緒安全和高效的特性原始碼HashMap執行緒
- ConcurrentHashMap 原始碼閱讀小結HashMap原始碼
- ConcurrentHashMap原始碼解析-Java7HashMap原始碼Java
- 死磕 java集合之ConcurrentHashMap原始碼分析(二)——擴容全解析JavaHashMap原始碼
- 死磕 java集合之ConcurrentHashMap原始碼分析(一)——插入元素全解析JavaHashMap原始碼
- JDK1.8 ConcurrentHashMap原始碼閱讀JDKHashMap原始碼
- Java7 ConcurrentHashMap原始碼淺析JavaHashMap原始碼
- 原始碼淺入淺出 Java ConcurrentHashMap原始碼JavaHashMap
- ConcurrentHashMap (jdk1.7)原始碼學習HashMapJDK原始碼
- 多執行緒高併發程式設計(10) -- ConcurrentHashMap原始碼分析執行緒程式設計HashMap原始碼
- ConcurrentHashMap基於JDK1.8原始碼剖析HashMapJDK原始碼
- ConcurrentHashMap原始碼解析,多執行緒擴容HashMap原始碼執行緒
- ConcurrentHashMap原始碼刨析(基於jdk1.7)HashMap原始碼JDK
- ConcurrentHashMap隨意分析HashMap
- 死磕 java集合之ConcurrentHashMap原始碼分析(三)——刪除元素全解析(內含彩蛋)JavaHashMap原始碼
- Retrofit原始碼分析三 原始碼分析原始碼
- Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析Java原始碼JDKHashMap
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼
- 集合原始碼分析[3]-ArrayList 原始碼分析原始碼
- Guava 原始碼分析之 EventBus 原始碼分析Guava原始碼
- 【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析JDK原始碼BloC
- 集合原始碼分析[1]-Collection 原始碼分析原始碼
- Android 原始碼分析之 AsyncTask 原始碼分析Android原始碼
- JDK原始碼閱讀(7):ConcurrentHashMap類閱讀筆記JDK原始碼HashMap筆記