ConcurrentHashMap,采用了一種“懶加載”的模式,只有到首次插入鍵值對的時候,才會真正的去初始化table數(shù)組。
1、空構(gòu)造函數(shù),默認桶大小16
2、指定桶初始容量的構(gòu)造器,必須是2次冪值
站在用戶的角度思考問題,與客戶深入溝通,找到桑珠孜網(wǎng)站設計與桑珠孜網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:成都網(wǎng)站設計、做網(wǎng)站、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、空間域名、虛擬主機、企業(yè)郵箱。業(yè)務覆蓋桑珠孜地區(qū)。
/**
* 指定table初始容量的構(gòu)造器.
* tableSizeFor會返回大于入?yún)ⅲ╥nitialCapacity + (initialCapacity >>> 1) + 1)的 最小2次冪值
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
3、根據(jù)已有的Map構(gòu)造
4、指定table初始容量和負載因子的構(gòu)造器
5、指定table初始容量、負載因子、并發(fā)級別的構(gòu)造器
/**
* 最大容量.
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默認初始容量
*/
private static final int DEFAULT_CAPACITY = 16;
/**
* 負載因子,為了兼容JDK1.8以前的版本而保留。
* JDK1.8中的ConcurrentHashMap的負載因子恒定為0.75
*/
private static final float LOAD_FACTOR = 0.75f;
/**
* 鏈表轉(zhuǎn)樹的閾值,即鏈接結(jié)點數(shù)大于8時, 鏈表轉(zhuǎn)換為樹.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 樹轉(zhuǎn)鏈表的閾值,即樹結(jié)點樹小于6時,樹轉(zhuǎn)換為鏈表.
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 在鏈表轉(zhuǎn)變成樹之前,還會有一次判斷:
* 即只有桶大小數(shù)量大于MIN_TREEIFY_CAPACITY,才會發(fā)生轉(zhuǎn)換。
* 這是為了避免在Table建立初期,多個鍵值對恰好被放入了同一個鏈表中而導致不必要的轉(zhuǎn)化。
*/
static final int MIN_TREEIFY_CAPACITY = 64;
/**
* 在樹轉(zhuǎn)變成鏈表之前,還會有一次判斷:
* 即只有桶的數(shù)量小于MIN_TRANSFER_STRIDE,才會發(fā)生轉(zhuǎn)換.
*/
private static final int MIN_TRANSFER_STRIDE = 16;
/**
* 用于在擴容時生成唯一的隨機數(shù).
*/
private static int RESIZE_STAMP_BITS = 16;
/**
* 可同時進行擴容操作的最大線程數(shù).
*/
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
static final int MOVED = -1; // 標識ForwardingNode結(jié)點
static final int TREEBIN = -2; // 標識紅黑樹的根結(jié)點
static final int RESERVED = -3; // 標識ReservationNode結(jié)點()
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
/**
* CPU核心數(shù),擴容時使用
*/
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Node數(shù)組,標識整個Map,首次插入元素時創(chuàng)建,大小總是2的冪次.
*/
transient volatile Node<K, V>[] table;
/**
* 擴容后的新Node數(shù)組,只有在擴容時才非空.
*/
private transient volatile Node<K, V>[] nextTable;
/**
* 控制table的初始化和擴容.
* 0 : 初始默認值
* -1 : 有線程正在進行table的初始化
* >0 : table初始化時使用的容量,或初始化/擴容完成后的threshold
* -(1 + nThreads) : 記錄正在執(zhí)行擴容任務的線程數(shù)
*/
private transient volatile int sizeCtl;
/**
* 擴容時需要用到的一個下標變量.
*/
private transient volatile int transferIndex;
/**
* 計數(shù)基值,當沒有線程競爭時,計數(shù)將加到該變量上。類似于LongAdder的base變量
*/
private transient volatile long baseCount;
/**
* 計數(shù)數(shù)組,出現(xiàn)并發(fā)沖突時使用。類似于LongAdder的cells數(shù)組
*/
private transient volatile CounterCell[] counterCells;
/**
* 自旋標識位,用于CounterCell[]擴容時使用。類似于LongAdder的cellsBusy變量
*/
private transient volatile int cellsBusy;
/**
* 插入鍵值對,<K,V>均不能為null.
*/
public V put(K key, V value) {
return putVal(key, value, false);
}
/**
* 實際的插入操作
*
* @param onlyIfAbsent true:僅當key不存在時,才插入
*/
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode()); // 再次計算hash值
/**
* 使用鏈表保存時,binCount記錄table[i]這個桶中所保存的節(jié)點數(shù);
* 使用紅黑樹保存時,binCount==2,保證put后更改計數(shù)值時能夠進行擴容檢查,同時不觸發(fā)紅黑樹化操作
*/
int binCount = 0;
for (Node<K, V>[] tab = table; ; ) { // 自旋插入結(jié)點,直到成功
Node<K, V> f;
int n, i, fh;
if (tab == null || (n = tab.length) == 0) // CASE1: 首次初始化table —— 懶加載
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // CASE2: table[i]對應的桶為null
// 注意下上面table[i]的索引i的計算方式:[ key的hash值 & (table.length-1) ]
// 這也是table容量必須為2的冪次的原因,讀者可以自己看下當table.length為2的冪次時,(table.length-1)的二進制形式的特點 —— 全是1
// 配合這種索引計算方式可以實現(xiàn)key的均勻分布,減少hash沖突
if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null))) // 插入一個鏈表結(jié)點
break;
} else if ((fh = f.hash) == MOVED) // CASE3: 發(fā)現(xiàn)ForwardingNode結(jié)點,說明此時table正在擴容,則嘗試協(xié)助數(shù)據(jù)遷移
tab = helpTransfer(tab, f); // 遷移數(shù)據(jù)方法
else { // CASE4: 出現(xiàn)hash沖突,也就是table[i]桶中已經(jīng)有了結(jié)點
V oldVal = null;
synchronized (f) { // 鎖住table[i]結(jié)點
if (tabAt(tab, i) == f) { // 再判斷一下table[i]是不是第一個結(jié)點, 防止其它線程的寫修改
if (fh >= 0) { // CASE4.1: table[i]是鏈表結(jié)點
binCount = 1;
for (Node<K, V> e = f; ; ++binCount) {
K ek;
// 找到“相等”的結(jié)點,判斷是否需要更新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) { // “尾插法”插入新結(jié)點
pred.next = new Node<K, V>(hash, key,
value, null);
break;
}
}
} else if (f instanceof TreeBin) { // CASE4.2: table[i]是紅黑樹結(jié)點
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); // 鏈表 -> 紅黑樹 轉(zhuǎn)換
if (oldVal != null) // 表明本次put操作只是替換了舊值,不用更改計數(shù)值
return oldVal;
break;
}
}
}
addCount(1L, binCount); // 計數(shù)值加1
return null;
}
private final Node<K, V>[] initTable() {
Node<K, V>[] tab;
int sc;
while ((tab = table) == null || tab.length == 0) { //自旋直到初始化成功
if ((sc = sizeCtl) < 0) // sizeCtl<0 說明table已經(jīng)正在初始化/擴容
Thread.yield();
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 將sizeCtl更新成-1,表示正在初始化中
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];
table = tab = nt;
sc = n - (n >>> 2); // 0.75n,負載因子
}
} finally {
sizeCtl = sc; // 設置threshold = 0.75 * table.length
}
break;
}
}
return tab;
}
/**
* 嘗試進行 鏈表 -> 紅黑樹 的轉(zhuǎn)換.
*/
private final void treeifyBin(Node<K, V>[] tab, int index) {
Node<K, V> b;
int n, sc;
if (tab != null) {
// CASE 1: table的容量 < MIN_TREEIFY_CAPACITY(64)時,直接進行table擴容,不進行紅黑樹轉(zhuǎn)換
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
// CASE 2: table的容量 ≥ MIN_TREEIFY_CAPACITY(64)時,進行鏈表 -> 紅黑樹的轉(zhuǎn)換
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) {
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類型包裝,并鏈接到table[index]中
setTabAt(tab, index, new TreeBin<K, V>(hd));
}
}
}
}
}
/**
* 根據(jù)key查找對應的value值
*
* @return 查找不到則返回null
*/
public V get(Object key) {
Node<K, V>[] tab;
Node<K, V> e, p;
int n, eh;
K ek;
int h = spread(key.hashCode()); // 重新計算key的hash值
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) { // CASE1、table[i]就是待查找的項,直接返回
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
} else if (eh < 0) //CASE2、hash值<0, 說明遇到非鏈表結(jié)點, 調(diào)用對應節(jié)點的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;
}
對于CASE2,重點看一下TreeBin結(jié)點的查找
1、TreeBin的查找
ConcurrentHashMap采用了一種類似讀寫鎖的方式:當線程持有寫鎖(修改紅黑樹)時,如果讀線程需要查找,不會像傳統(tǒng)的讀寫鎖那樣阻塞等待,而是轉(zhuǎn)而以鏈表的形式進行查找(TreeBin本身時Node類型的子類,所有擁有Node的所有字段)
/**
* 從根結(jié)點開始遍歷查找,找到“相等”的結(jié)點就返回它,沒找到就返回null
* 當存在寫鎖時,以鏈表方式進行查找
*/
final Node<K, V> find(int h, Object k) {
if (k != null) {
for (Node<K, V> e = first; e != null; ) {
int s;
K ek;
/**
* 兩種特殊情況下以鏈表的方式進行查找:
* 1. 有線程正持有寫鎖,這樣做能夠不阻塞讀線程
* 2. 有線程等待獲取寫鎖,不再繼續(xù)加讀鎖,相當于“寫優(yōu)先”模式
*/
if (((s = lockState) & (WAITER | WRITER)) != 0) {
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
e = e.next; // 鏈表形式
}
// 讀線程數(shù)量加1,讀狀態(tài)進行累加
else if (U.compareAndSwapInt(this, LOCKSTATE, s, s + READER)) {
TreeNode<K, V> r, p;
try {
p = ((r = root) == null ? null :
r.findTreeNode(h, k, null));
} finally {
Thread w;
// 如果當前線程是最后一個讀線程,且有寫線程因為讀鎖而阻塞,則喚醒寫線程,嘗試獲取寫鎖
if (U.getAndAddInt(this, LOCKSTATE, -READER) == (READER | WAITER) && (w = waiter) != null)
LockSupport.unpark(w);
}
return p;
}
}
}
return null;
}
網(wǎng)頁題目:多線程(十五、ConcurrentHashMap原理(2)類和方法分析)
網(wǎng)站地址:http://www.rwnh.cn/article20/jsccco.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導航、品牌網(wǎng)站設計、網(wǎng)站收錄、網(wǎng)站改版、動態(tài)網(wǎng)站、網(wǎng)站營銷
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)