分析ConcurrentHashMap的原始碼實現(jdk1.8)
ConcurrentHashMap
不僅實現了多執行緒的同步讀寫而且輕量級,這是它相比於HashMap
和HashTable
的優勢。HashMap
是執行緒不安全的,它沒有提供任何的同步機制,多執行緒併發訪問會有問題。HashTable
雖然提供了同步機制,但是它是通過整個物件加鎖達到同步的,是重量級的,併發性較低。下面將通過put()和get()方法,分析ConcurrentHashMap
的主要實現。
一、ConcurrentHashMap的設計
ConcurrentHashMap
將所有的key-value抽象封裝在Node
類中,資料儲存結構是這樣的:整體上分桶(table[i]
),將不同雜湊值的key-value存放在陣列table[]
ConcurrentHashMap
紅黑樹相關程式碼也是其作者改編自CLR,相關演算法可參考紅黑樹詳解。二、ConcurrentHashMap的建構函式
這裡分析ConcurrentHashMap(int initialCapacity);
建構函式,其它建構函式大同小異。
1、ConcurrentHashMap
中的成員變數介紹:有許多核心的概念要在分析原始碼之前介紹一下,寫在原始碼註釋中:
//雜湊表的最大長度
private static final int MAXIMUM_CAPACITY = 1 << 30;
//雜湊表預設長度
private static final int DEFAULT_CAPACITY = 16;
//負載因子,在雜湊表的容量大於陣列長度的3/4時會觸發擴容
private static final float LOAD_FACTOR = 0.75f;
//當雜湊表桶中連結串列過長的時候,會觸發樹化,即在連結串列長度大於8的時候,連結串列會轉化為紅黑樹
//理想狀態下,每個桶中元素的數量是符合泊松分佈的,在負載因子為0.75時,一個桶中元素個數大於8個的概率在千萬分之一,
//可以忽略不計,所以設計合理的雜湊表樹化的概率是很低的
static final int TREEIFY_THRESHOLD = 8;
//在擴容時,發現樹的長度小於6,紅黑樹會退化為連結串列
static final int UNTREEIFY_THRESHOLD = 6;
//連結串列樹化的條件還要陣列長度大於64,雖然桶中元素大於8個,但是陣列長度小於64是不會樹化的,防止雜湊表設計不合理導致樹化
static final int MIN_TREEIFY_CAPACITY = 64;
//多個執行緒是可以同時輔助擴容的,一個執行緒最低擴容的區間長度為16
private static final int MIN_TRANSFER_STRIDE = 16;
//用來輔助計算sizeCtl值的,在下面的resizeStamp()函式中會介紹
private static final int RESIZE_STAMP_BITS = 16;
//在擴容時輔助計算sizeCtl的值,用來表示當前正在擴容的執行緒數
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
//ForwardingNode的hash值,代表該節點已經擴容完成了,節點已經被轉移到了新的陣列中
static final int MOVED = -1;
//紅黑樹的節點(TreeBin)的hash值
static final int TREEBIN = -2;
//掩碼,輔助計算雜湊值,確保雜湊值為正數
static final int HASH_BITS = 0x7fffffff;
//機器CPU核心數,在擴容時輔助判斷最多同時工作的執行緒數
static final int NCPU = Runtime.getRuntime().availableProcessors();
//雜湊表中所有的節點存在該陣列中,把每一個位置稱為一個桶
transient volatile Node<K,V>[] table;
//擴容時新的陣列,用於將table中的資料copy到該表中
private transient volatile Node<K,V>[] nextTable;
//大於0的時候,如果還未初始化table,則代表了陣列長度,否則代表雜湊表的容量(陣列長度的3/4)
//-1是陣列正在被初始化
//小於-1的時候,用來表示當前有多少個執行緒在擴容,此時該值是resizeStamp()函式計算出來的,
//後16位的實際數值減1代表正在擴容的執行緒(sizeCtl & 0xffff - 1),後面會具體介紹
private transient volatile int sizeCtl;
2、ConcurrentHashMap(int initialCapacity)
建構函式:傳入了雜湊表初始陣列長度值,然後根據該值計算sizeCtl 的值。
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
//不大於最大容量時,根據initialCapacity計算陣列的初始長度。
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
3、tableSizeFor()
函式,用來計算大於c的最小的2^n方的一個數值,因為陣列長度只能是2的冪次方,所以,該方法可以用來找到符合要求的陣列長度值。
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
三、ConcurrentHashMap的put()方法
1、put()
方法呼叫了putVal()
:
該方法主體流程如下:
①如果陣列還沒有初始化,則先初始化陣列,長度為sizeCtl
;
②計算雜湊值,找到在陣列中的位置,如果該位置為null,代表還沒有資料放入,則直接將key-value封裝成Node節點,放到該位置;
③如果該位置hash==MOVE
,代表陣列正在擴容,且此位置已經被移動到新陣列中,則讓該執行緒去輔助擴容;
④該位置是一個連結串列,找到連結串列相應的位置插入節點或者更新節點,在插入後,如果長度大於8,要將連結串列轉換為紅黑樹;
⑤該位置是一棵紅黑樹,則將節點插入樹中,或者更新樹中的節點。
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
//根據key的hashCode值,計算hash值
int hash = spread(key.hashCode());
int binCount = 0;
//這裡是一個for迴圈,停止條件在迴圈裡面,防止一些由於CAS失敗(初始化陣列等)導致插入資料失敗
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//初始化陣列
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//(n - 1) & hash)計算應該雜湊值,拿到該位置的資料
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) {
//hash值大於0,說明該位置是一個連結串列
if (fh >= 0) {
//記錄連結串列長度
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//遍歷連結串列,如果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;
//將key-value插入到連結串列尾部
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;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
//連結串列長度大於8,樹化連結串列
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//雜湊表中實際儲存資料長度加1
addCount(1L, binCount);
return null;
}
2、spread()
方法計算hash值:將hashCode右移16位,然後再異或,這樣對hash值做了一次擾動,因為計算的雜湊值通常由低位起決定作用(hash&table.legth
),經過擾動之後,高位也可以對hash值起到一定的影響,& HASH_BITS
之後,保證值為正數。
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
3、initTable()
初始化陣列:
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(); //長度小於0,出錯,放棄執行緒執行
//CAS,將sizeCtl設定為-1,代表此時正在初始化陣列,其它執行緒同時執行到這裡則會失敗
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;
//將容量設定為陣列長度的3/4
sc = n - (n >>> 2);
}
} finally {
//將容量設定為陣列長度的3/4
sizeCtl = sc;
}
break;
}
}
return tab;
}
4、helpTransfer()
函式,用來輔助擴容的函式
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
Node<K,V>[] nextTab; int sc;
//擴容時,舊陣列table中放入的節點是ForwardingNode型別,nextTable是擴容時新建的陣列,將舊陣列中的資料拷貝到該陣列中
if (tab != null && (f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
//根據陣列長度計算一個標記,下面詳細介紹該函式的作用
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
//陣列沒有還未被擴容的區間或者sizeCtl值有變化,則執行緒不需要去輔助擴容
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
//將sizeCtl加1,代表即將又有一個執行緒去輔助擴容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
//實際執行擴容的函式
transfer(tab, nextTab);
break;
}
}
//完成了擴容,返回新陣列
return nextTab;
}
//沒有去輔助擴容,返回舊的陣列
return table;
}
5、分析resizeStamp()
函式:引數n一般就是陣列長度,RESIZE_STAMP_BITS
是16,Integer.numberOfLeadingZeros(n)
函式用於計算一個int型值二進位制中第一個1前面0的個數,然後將該值的第16位置1,在左移16位之後(賦值給sizeCtl
),保證是一個負數。如n=16,前置0的個數是27(11011
)個,則返回值為00000000000000001000000000011011
。
static final int resizeStamp(int n) {
return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n += 8; i <<= 8; }
if (i >>> 28 == 0) { n += 4; i <<= 4; }
if (i >>> 30 == 0) { n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}
6、treeifyBin()
函式,將連結串列結構轉化為紅黑樹:
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n;
if (tab != null) {
//雖然桶中長度大於8,但是陣列長度小於64,則去擴容,而不是轉化成紅黑樹
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
//將連結串列的Node節點轉化成TreeNode節點
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
//TreeBin的建構函式中會生成紅黑樹的結構
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
7、tryPresize()
擴容從這裡開始,進行擴容前的準備,該函式在正在擴容的時候只會執行一次while
裡的迴圈,它的作用是開啟一次擴容。
private final void tryPresize(int size) {
//計算擴容後陣列長度,為現在陣列長度的2倍
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
//sizeCtl<0說明已經在擴容了,則放棄返回,可以防止該函式在擴容的時候被執行多次
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
//陣列還未初始化,去初始化
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new
相關推薦
分析ConcurrentHashMap的原始碼實現(jdk1.8)
ConcurrentHashMap不僅實現了多執行緒的同步讀寫而且輕量級,這是它相比於HashMap和HashTable的優勢。HashMap是執行緒不安全的,它沒有提供任何的同步機制,多執行緒併發訪問會有問題。HashTable雖然提供了同步機制,但是它是通過整個物件加鎖達到同步的,是重量
Java執行緒池實現原理與原始碼解析(jdk1.8)
為什麼需要執行緒池?
執行緒池能夠對執行緒進行統一分配,調優和監控:
- 降低資源消耗(執行緒無限制地建立,然後使用完畢後銷燬)
- 提高響應速度(無須建立執行緒)
- 提高執行緒的可管理性
Java是如何實現和管理執行緒池的?
從JDK 5開始,把
PHP實現微信退款的分析與原始碼實現
* 1.微信退款到零錢要求必傳證書,需要到https://pay.weixin.qq.com 賬戶中心->賬戶設定->API安全->下載證書,然後修改程式碼中的證書路徑
* 2.該檔案需放到支付授權目錄下,可以在微信支付商戶平臺->產品中心->開發配置中設定。&nb
spring MVC執行過程分析與原始碼實現
spring mvc 啟動過程可分為如下3步:
=============================================
web.xml
Dispa
HashMap原始碼解讀(jdk1.8)
本文基於jdk1.8解讀HashMap關鍵程式碼
HashMap是非執行緒安全的,在多執行緒環境下要使用ConcurrentHashMap
儲存結構
HashMap的儲存結構是陣列 + 連結串列 + 紅黑樹,當連結串列的長度大於等於8時,連結轉成紅黑樹
初始化
HashM
ConcurrentHashMap原始碼實現
concurrentHashmap和HashMap的區別:
concurrentHashmap和HashMap大多數下的使用場景基本一致,但最大的區別就是concurrentHashmap是執行緒安全
死磕Java之聊聊HashSet原始碼(基於JDK1.8)
HashSet的UML圖
HashSet的成員變數及其含義
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, C
死磕Java之聊聊ArrayList原始碼(基於JDK1.8)
工作快一年了,近期打算研究一下JDK的原始碼,也就因此有了死磕java系列
ArrayList 是一個數組佇列,相當於動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Clo
HashMap原始碼解析jdk1.8:初始化resize,新增put,獲取get
原始碼解析有參考以下部落格:
http://www.cnblogs.com/jzb-blog/p/6637823.html
HashMap:
以k-v鍵值對儲存格式的容器,key,value都可以為空,key不重複,非執行緒安全(執行緒安全請使用Concur
HashMap原始碼解析(jdk1.8)
寫在篇頭
其實這是在我寫完下邊所有方法解析後寫的。每次看原始碼,有些時候都不知道每一步的意義在哪裡,缺少了自己的思考,直接看會枯燥,甚至不知所云。今天突然想換種說明方式。為什麼會有HashMap這種結構,為了實現什麼目的?為什麼用這種結構?比其他結構的好在哪裡
CentOS7.5原始碼安裝JDK1.8詳細過程
第一步 解除安裝系統自帶的OpenJDK以及相關的java檔案
用root管理員登入伺服器
①在命令視窗鍵入:
java -version
可以看到系統自帶的OpenJDK版本資訊。
②在命令視窗鍵入:
rpm -qa | grep java
命令說明:
Linux原始碼安裝JDK1.8
Linux原始碼安裝Java
1、到官網下載
jdk-8u131-linux-x64.tar.gz
官網地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-21
原始碼解析JDK1.8-HashMap連結串列成環的問題解決方案
前言
上篇文章詳解介紹了HashMap在JDK1.7版本中連結串列成環的原因,今天介紹下JDK1.8針對HashMap執行緒安全問題的解決方案。
jdk1.8 擴容原始碼解析
public class HashMap<K,V> extends AbstractMap<K,V>&n
JDK1.8 ConcurrentHashMap原始碼分析
文章目錄
ConcurrentHashMap資料結構
類的繼承關係
類的內部類
重要的屬性
類的建構函式
ConcurrentHashMap()型建構函式
ConcurrentHashMap(i
ConcurrentHashMap JDK1.8中結構原理及原始碼分析
注:本文根據網路和部分書籍整理基於JDK1.7書寫,如有雷同敬請諒解 歡迎指正文中的錯誤之處。
資料結構
ConcurrentHashMap 1.8 拋棄了Segment分段鎖機制,採用Node + CAS + Synchronized來保證併發安全進行實現
Java中HashMap底層實現原理(JDK1.8)原始碼分析
在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查詢的效率較低。而JDK1.8中,HashMap採用位桶+
(轉載)Java中HashMap底層實現原理(JDK1.8)原始碼分析
近期在看一些java底層的東西,看到一篇分析hashMap不錯的文章,跟大家分享一下。
在JDK1.6,JDK1.7中,HashMap採用位桶+連結串列實現,即使用連結串列處理衝突,同一hash值的連結串列都儲存在一個連結串列裡。但是當位於一個桶中的元素較多,即hash值
JDK1.8中ArrayList的實現原理及原始碼分析
一、概述
ArrayList是Java開發中使用比較頻繁的一個類,通過對原始碼的解讀,可以瞭解ArrayList的內部結構以及實現方法,清楚它的優缺點,以便我們在程式設計時靈活運用。
二、原始碼分析
2.1 類結構
JDK1.8原始碼中的A
Java中HashMap底層實現原理(JDK1.8)源碼分析
blank imp dash logs || 屬性 lte das ces 這幾天學習了HashMap的底層實現,但是發現好幾個版本的,代碼不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一樣,原來他們沒有指定JDK版本,很多文章都是舊版本J
ConcurrentHashMap的JDK1.8實現
也說 con ping 線程 shc 正在 nali break 頭部 今天我們介紹一下ConcurrentHashMap在JDK1.8中的實現。基本結構
ConcurrentHashMap在1.8中的實現,相比於1.7的版本基本上全部都變掉了。首