Java并發(fā)源碼分析ConcurrentHashMap線程集合
簡(jiǎn)介
ConcurrentHashMap
是一個(gè)線程安全的集合,底層是通過對(duì)指定索引位置上的節(jié)點(diǎn)進(jìn)行加鎖,而不是對(duì)整個(gè)數(shù)組加鎖,當(dāng)一個(gè)線程對(duì)指定索引位置上的節(jié)點(diǎn)加了鎖之后,其它線程就不能對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作,但可以對(duì)其它的索引位置上的節(jié)點(diǎn)操作,ConcurrentHashMap與HashMap
有許多相似的地方,ConcurrentHashMap
只是在一些產(chǎn)生線程安全的地方加了鎖,如果對(duì)HashMap
了解的話,再來看ConcurrentHashMap
就簡(jiǎn)單許多。
常量
/** 數(shù)組最大容量大小(1073741824) */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** 默認(rèn)的初始容量大小 */ private static final int DEFAULT_CAPACITY = 16; /** 默認(rèn)的并發(fā)等級(jí) */ private static final int DEFAULT_CONCURRENCY_LEVEL = 16; /** 負(fù)載因子 */ private static final float LOAD_FACTOR = 0.75f; /** 鏈表轉(zhuǎn)換為紅黑樹的閾值 */ static final int TREEIFY_THRESHOLD = 8; /** 紅黑樹轉(zhuǎn)換為鏈表的閾值 */ static final int UNTREEIFY_THRESHOLD = 6; /** * 最小樹化閾值 * 當(dāng)鏈表中的節(jié)點(diǎn)數(shù)量大于等于8時(shí),如果數(shù)組的長度小于最小樹化閾值則不會(huì)將鏈表轉(zhuǎn)換為紅黑樹,而是對(duì)數(shù)組進(jìn)行擴(kuò)容 * 將鏈表中的節(jié)點(diǎn)分散到別的索引節(jié)點(diǎn)中去,只有當(dāng)數(shù)組的長度大于等于最小樹化閾值則會(huì)將鏈表轉(zhuǎn)換為紅黑樹 */ static final int MIN_TREEIFY_CAPACITY = 64; /** 擴(kuò)容時(shí),每個(gè)cpu最少需要負(fù)責(zé)的區(qū)域長度 */ private static final int MIN_TRANSFER_STRIDE = 16; /** 擴(kuò)容時(shí)生成標(biāo)記的二進(jìn)制所在位置 */ private static int RESIZE_STAMP_BITS = 16; /** 擴(kuò)容時(shí)記錄擴(kuò)容容量的標(biāo)記的二進(jìn)制所在位置 */ private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; //節(jié)點(diǎn)正在移動(dòng) static final int MOVED = -1; //節(jié)點(diǎn)已被樹化 static final int TREEBIN = -2; static final int RESERVED = -3; // hash for transient reservations //32位二進(jìn)制中正數(shù)最大的值,主要用于對(duì)key的hash值進(jìn)行二次運(yùn)算 static final int HASH_BITS = 0x7fffffff; /** 當(dāng)前機(jī)器的cpu數(shù)量 */ static final int NCPU = Runtime.getRuntime().availableProcessors(); /** * 存放元素的數(shù)組對(duì)象 * 只有在第一次添加元素的時(shí)候進(jìn)行初始化 * 如果沒有指定數(shù)組容量則使用默認(rèn)的容量16進(jìn)行初始化 * 數(shù)組容量為2的次方,如果指定的數(shù)組容量不是2的次方則會(huì)進(jìn)行計(jì)算獲取到大于指定容量并是2的次方的數(shù) */ transient volatile Node<K, V>[] table; /** 擴(kuò)容后的新數(shù)組,如果該數(shù)組不為空則說明當(dāng)前有其它線程正在進(jìn)行擴(kuò)容 */ private transient volatile Node<K, V>[] nextTable; /** 基本計(jì)數(shù)器值,在沒有線程爭(zhēng)用的時(shí)候才會(huì)使用 */ private transient volatile long baseCount; /** * -1時(shí)則說明數(shù)組對(duì)象正在初始化 * 如果數(shù)組未被初始化時(shí)該值為數(shù)組初始化時(shí)的大小 * 如果數(shù)組已被初始化該值為數(shù)組擴(kuò)容的閾值 * -N:低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M,M-1則是當(dāng)前正在擴(kuò)容的線程數(shù)量 */ private transient volatile int sizeCtl; /** * 擴(kuò)容時(shí)需要轉(zhuǎn)移舊數(shù)組中的剩余的索引位置長度 * 每個(gè)線程最少負(fù)責(zé)16個(gè)索引位置上的節(jié)點(diǎn)轉(zhuǎn)移 */ private transient volatile int transferIndex; /** * 計(jì)數(shù)器數(shù)組的鎖標(biāo)識(shí) * 用與初始化計(jì)數(shù)器數(shù)組以及擴(kuò)容和創(chuàng)建計(jì)數(shù)器對(duì)象的時(shí)候使用 */ private transient volatile int cellsBusy; /** 計(jì)數(shù)器數(shù)組 */ private transient volatile CounterCell[] counterCells;
構(gòu)造方法
/** * 創(chuàng)建一個(gè)默認(rèn)容量為16的數(shù)組對(duì)象 * 該構(gòu)造方法中并沒有執(zhí)行任何操作 * 并沒有創(chuàng)建默認(rèn)容量的數(shù)組對(duì)象 * 只有在第一次添加元素的時(shí)候才會(huì)初始化數(shù)組對(duì)象 */ public ConcurrentHashMap() { } /** * 創(chuàng)建一個(gè)指定初始容量的數(shù)組對(duì)象 * @param initialCapacity 指定的初始容量 */ public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException(); //校驗(yàn)指定的初始容量大小是否大于等于最大容量大小的一半 //如果大于等于最大容量大小的一半則在后續(xù)第一次添加元素的時(shí)候使用最大容量大小創(chuàng)建數(shù)組 //如果小于最大容量大小的一半則根據(jù)指定容量來計(jì)算最接近指定容量大小并大于指定容量的2的次方 //此處有一個(gè)疑問,指定容量大小如果等于最大容量大小的一半則說明指定容量大小是2的29次方,為什么不使用指定容量大小,而是使用最大的容量大小? //從tableSizeFor方法也能看出,如果指定的初始容量大小本來就是2的次方的話并不會(huì)使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象 //而是使用大于指定初始容量大小的2的次方來創(chuàng)建數(shù)組對(duì)象,比如指定的初始容量大小為16,那就會(huì)使用32來創(chuàng)建數(shù)組對(duì)象 //在HashMap中如果指定的初始容量大小本來就是2的次方的話則會(huì)使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象 //并不會(huì)像ConcurrentHashMap一樣會(huì)取比當(dāng)前指定的2的次方的初始容量大小大的2的次方的容量大小來創(chuàng)建數(shù)組對(duì)象 int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1)); //將計(jì)算后的容量大小賦值給sizeCtl等待初始化 this.sizeCtl = cap; } /** * 根據(jù)指定的集合中的元素來創(chuàng)建新的數(shù)組對(duì)象 * @param m the map */ public ConcurrentHashMap(Map<? extends K, ? extends V> m) { //使用默認(rèn)的初始容量大小等待初始化 this.sizeCtl = DEFAULT_CAPACITY; //初始化新的數(shù)組對(duì)象并將指定集合中的元素添加到新的數(shù)組對(duì)象中 putAll(m); } /** * 根據(jù)指定的初始容量大小和指定的負(fù)載因子來創(chuàng)建新的數(shù)組對(duì)象 */ public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); } /** * 根據(jù)指定的初始容量大小和指定的負(fù)載因子和并發(fā)數(shù)來創(chuàng)建新的數(shù)組對(duì)象 */ public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (initialCapacity < concurrencyLevel) //初始容量大小小于并發(fā)數(shù)則使用并發(fā)數(shù)來創(chuàng)建數(shù)組對(duì)象 initialCapacity = concurrencyLevel; //通過計(jì)算獲取到擴(kuò)容的閾值 long size = (long) (1.0 + (long) initialCapacity / loadFactor); //如果閾值大于等于最大容量大小則使用最大容量大小來創(chuàng)建新的數(shù)組對(duì)象 //如果閾值小于最大容量大小則使用閾值來計(jì)算獲取到大于閾值并是2的次方的值 //使用計(jì)算后的值來創(chuàng)建新的數(shù)組對(duì)象 int cap = (size >= (long) MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int) size); //將計(jì)算后的容量大小賦值給sizeCtl等待初始化 this.sizeCtl = cap; }
在構(gòu)造方法中并沒有初始化數(shù)組對(duì)象,而是在第一次添加元素的時(shí)候才會(huì)進(jìn)行初始化,根據(jù)指定的初始容量大小和指定的負(fù)載因子進(jìn)行初始化,如果沒有指定初始容量或負(fù)載因子則使用默認(rèn)的,而數(shù)組的大小必須是2的次方,最大的數(shù)組大小則是2的30次方,如果大于2的30次方的話,數(shù)組則不會(huì)將繼續(xù)進(jìn)行擴(kuò)容,當(dāng)數(shù)組的容量大小已到達(dá)最大時(shí),后續(xù)添加的元素則會(huì)掛載到鏈表或紅黑樹上,如果說指定的初始容量不是2的次方時(shí)則會(huì)調(diào)用tableSizeFor
方法計(jì)算出大于指定初始容量并且是2的次方的值,如果說指定的初始容量本身就是2的次方時(shí)通過計(jì)算出的值還是本身。
此處就有一個(gè)疑問,本身的值不是2的次方的時(shí)候會(huì)通過計(jì)算獲取到大于指定的初始容量并且是最接近指定的初始容量的2的次方的值,如果本身的值就是2的次方的時(shí)候則會(huì)取本身的值,但是在第二個(gè)構(gòu)造方法中的tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1))
方法是有問題的,假如initialCapacity 為16
,按理來說最終數(shù)組初始化的容量大小應(yīng)該也為16,但是經(jīng)過該方法計(jì)算最終的數(shù)組初始化的容量大小為32,雖然該構(gòu)造方法有問題,但是并不影響。
put
public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { if (key == null || value == null) //key和value不能為空 throw new NullPointerException(); //將key的hash值的高16位二進(jìn)制參加運(yùn)算獲取到新的hash值 int hash = spread(key.hashCode()); //指定索引位置上節(jié)點(diǎn)的數(shù)量 int binCount = 0; for (Node<K, V>[] tab = table; ; ) { //數(shù)組中指定索引的節(jié)點(diǎn) Node<K, V> f; //n 數(shù)組長度 //i key所在的數(shù)組索引位置 //fh 指定索引的節(jié)點(diǎn)的hash值 int n, i, fh; //校驗(yàn)數(shù)組是否已經(jīng)初始化 if (tab == null || (n = tab.length) == 0) //數(shù)組未初始化則初始化數(shù)組 //并返回初始化完成的數(shù)組對(duì)象 tab = initTable(); //通過數(shù)組索引長度與key的hash值進(jìn)行與運(yùn)算獲取到指定索引位置上的元素節(jié)點(diǎn),并校驗(yàn)元素節(jié)點(diǎn)是否為空 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //指定索引位置上的節(jié)點(diǎn)不存在則使用傳遞進(jìn)來的key和value創(chuàng)建新的節(jié)點(diǎn) //并將新的節(jié)點(diǎn)放置到指定索引位置上 if (casTabAt(tab, i, null,new Node<K, V>(hash, key, value, null))) //節(jié)點(diǎn)添加成功則退出循環(huán) break; //校驗(yàn)索引位置上的節(jié)點(diǎn)是否正在移動(dòng) } else if ((fh = f.hash) == MOVED) //如果索引位置上的節(jié)點(diǎn)正在移動(dòng)則幫忙移動(dòng)節(jié)點(diǎn)到新的數(shù)組中 //協(xié)助擴(kuò)容 tab = helpTransfer(tab, f); else { V oldVal = null; //使用鎖鎖住指定索引位置上的節(jié)點(diǎn) //這樣其它線程就不能對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作 //其它線程可以對(duì)其它索引位置上的節(jié)點(diǎn)進(jìn)行操作 synchronized (f) { //校驗(yàn)指定索引位置上的節(jié)點(diǎn)是否被更改 if (tabAt(tab, i) == f) { //校驗(yàn)節(jié)點(diǎn)的hash值是否大于等于0 //如果節(jié)點(diǎn)的hash值大于等于0則說明該索引位置上只有一個(gè)節(jié)點(diǎn)或節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn) if (fh >= 0) { binCount = 1; //從指定索引位置上的節(jié)點(diǎn)開始遍歷 for (Node<K, V> e = f; ; ++binCount) { K ek; //校驗(yàn)遍歷到的節(jié)點(diǎn)的hash值以及key是否與傳遞的key和計(jì)算的hash值相同 //如果相同則根據(jù)onlyIfAbsent參數(shù)來決定是否需要使用新的value替換舊的value if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { //獲取舊的value oldVal = e.val; if (!onlyIfAbsent) //使用新的value替換舊的value e.val = value; break; } //遍歷到的節(jié)點(diǎn)的hash值以及key不相同 //則判斷當(dāng)前遍歷到的節(jié)點(diǎn)是否是鏈表上的最后一個(gè)節(jié)點(diǎn) //如果不是最后一個(gè)節(jié)點(diǎn)則繼續(xù)遍歷 //如果是最后一個(gè)節(jié)點(diǎn)則為key和value創(chuàng)建一個(gè)新的節(jié)點(diǎn) //并將原尾節(jié)點(diǎn)的next指針指向新創(chuàng)建的節(jié)點(diǎn) Node<K, V> pred = e; if ((e = e.next) == null) { pred.next = new Node<K, V>(hash, key, value, null); break; } } //校驗(yàn)節(jié)點(diǎn)是否是一個(gè)紅黑樹節(jié)點(diǎn) } else if (f instanceof TreeBin) { Node<K, V> p; binCount = 2; //將指定的key和value封裝成一個(gè)樹節(jié)點(diǎn)并添加到紅黑樹中 //如果紅黑樹中已經(jīng)存在相同key的節(jié)點(diǎn)則根據(jù)onlyIfAbsent參數(shù)來決定是否使用新的value替換紅黑樹中的節(jié)點(diǎn)的value if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key,value)) != null) { //獲取節(jié)點(diǎn)中的舊值 oldVal = p.val; if (!onlyIfAbsent) //替換節(jié)點(diǎn)中的value p.val = value; } } } } if (binCount != 0) { //校驗(yàn)索引位置上的節(jié)點(diǎn)的數(shù)量是否到達(dá)樹化的閾值 //如果該索引位置上的節(jié)點(diǎn)本來就是樹節(jié)點(diǎn)則不會(huì)繼續(xù)樹化 //只有當(dāng)索引位置上的節(jié)點(diǎn)是鏈表節(jié)點(diǎn)并且節(jié)點(diǎn)的數(shù)量大于等于8的時(shí)候才會(huì)樹化 if (binCount >= TREEIFY_THRESHOLD) //索引位置上的節(jié)點(diǎn)數(shù)量已經(jīng)到達(dá)樹化的閾值 //則將該索引位置上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn) treeifyBin(tab, i); if (oldVal != null) //返回被替換的舊值 return oldVal; break; } } } //更新集合中元素節(jié)點(diǎn)的數(shù)量,并校驗(yàn)是否需要擴(kuò)容 addCount(1L, binCount); return null; }
其實(shí)putval
中的邏輯并不難,主要還是putval
中調(diào)用的其它一些方法比較難以理解,我們就一步步的來看。
首先如果key
和value
為null
的情況下是不能添加到當(dāng)前集合中的,再看spread
方法,該方法是將key
本身的hash
值的高16位參加運(yùn)算獲取到新的hash
值,如果數(shù)組的長度的二進(jìn)制是在低16位二進(jìn)制中就會(huì)導(dǎo)致key
高16位的二進(jìn)制沒有參加運(yùn)算,容易導(dǎo)致運(yùn)算后的key
的索引位置發(fā)生沖突,所以需要將高16位二進(jìn)制無符號(hào)右移16位與原hash
值的二進(jìn)制進(jìn)行運(yùn)算減少索引沖突。
static final int spread(int h) { return (h ^ (h >>> 16)) & HASH_BITS; }
為什么要與HASH_BITS進(jìn)行與運(yùn)算呢? HASH_BITS
是32位二進(jìn)制中最大的正整數(shù),與HASH_BITS
進(jìn)行與運(yùn)算其實(shí)就是讓計(jì)算后的hash
值保持在正整數(shù)。
后續(xù)整個(gè)代碼都被for
循環(huán)包裹著,只有當(dāng)添加元素或替換元素成功時(shí)才會(huì)退出,而for
循環(huán)里總共有4個(gè)大的分支。
1.數(shù)組未初始化,此時(shí)就需要調(diào)用initTable
方法初始化數(shù)組對(duì)象,之前也說過數(shù)組對(duì)象是在第一次添加元素的時(shí)候初始化的,這是一種懶加載的思想,當(dāng)你使用到的時(shí)候才會(huì)去初始化。
initTable
private final Node<K, V>[] initTable() { Node<K, V>[] tab; int sc; //校驗(yàn)數(shù)組是否未被初始化 while ((tab = table) == null || tab.length == 0) { if ((sc = sizeCtl) < 0) //數(shù)組長度小于0則說明其它線程正在準(zhǔn)備初始化數(shù)組 //當(dāng)前線程則需要讓出cpu讓其它線程初始化數(shù)組 Thread.yield(); //通過cas操作將sizeCtl修改成-1,告知其它線程當(dāng)前線程正在準(zhǔn)備初始化數(shù)組 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { //如果指定了初始容量則使用指定的初始容量來創(chuàng)建數(shù)組 //如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; //初始化數(shù)組 @SuppressWarnings("unchecked") Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n]; //將初始化的數(shù)組對(duì)象賦值給當(dāng)前集合中的數(shù)組對(duì)象 //并賦值給tab,下一次循環(huán)校驗(yàn)則會(huì)發(fā)現(xiàn)tab不為空則會(huì)退出循環(huán)并退出當(dāng)前方法 table = tab = nt; //計(jì)算數(shù)組擴(kuò)容的閾值 sc = n - (n >>> 2); } } finally { //如果try中的代碼塊執(zhí)行失敗時(shí)sizeCtl則是原先數(shù)組的長度 //執(zhí)行成功時(shí)sizeCtl則是擴(kuò)容的閾值 sizeCtl = sc; } break; } } //數(shù)組已被初始化返回?cái)?shù)組 return tab; }
我們來看initTable
方法中的代碼,首先whlie
循環(huán)中校驗(yàn)了數(shù)組是否還沒被初始化,當(dāng)數(shù)組未被初始化的時(shí)候,當(dāng)前線程則會(huì)去嘗試初始化數(shù)組,直到數(shù)組被當(dāng)前線程或者其它線程初始化成功。
先看(sc = sizeCtl) < 0
這段代碼,sizeCtl
小于0分為兩種情況,sizeCtl為-1
的時(shí)候則說明當(dāng)前有其它線程正在初始化數(shù)組對(duì)象,sizeCtl小于-1
的時(shí)候則說明當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,從當(dāng)前情況來看數(shù)組正在進(jìn)行擴(kuò)容的這種情況是不大可能存在的,所以說只有可能其它線程正在進(jìn)行初始化數(shù)組,當(dāng)其它線程正在初始化數(shù)組對(duì)象時(shí),那當(dāng)前線程則需要讓出cpu
的執(zhí)行權(quán),等待初始化數(shù)組對(duì)象的線程執(zhí)行完成。
再看后一個(gè)判斷,該判斷是一個(gè)cas
操作,將sizeCtl
修改成-1,告知其它線程當(dāng)前線程正在初始化數(shù)組,如果當(dāng)前線程修改失敗,則說明有其它線程搶先修改了sizeCtl
的值,那當(dāng)前線程則需要重新走while
循環(huán)來查看數(shù)組是否初始化完成,如果沒有完成那需要讓出cpu
的執(zhí)行權(quán),如果數(shù)組已經(jīng)被其它線程初始化完成,那當(dāng)前線程則可以對(duì)數(shù)組進(jìn)行操作,如果說當(dāng)前線程執(zhí)行cas
操作成功,則會(huì)先查看是否指定了數(shù)組初始的容量大小,如果指定了則使用指定的初始容量大小來創(chuàng)建數(shù)組對(duì)象,如果沒有指定則使用默認(rèn)的初始容量大小16來創(chuàng)建數(shù)組對(duì)象,該指定的初始容量并非是真正的初始容量,如果說指定的初始容量是2的次方則會(huì)使用該容量大小來創(chuàng)建數(shù)組對(duì)象,如果說指定的初始容量不是2的次方的時(shí)候則會(huì)通過計(jì)算來獲取大于這個(gè)指定的初始容量并且是最接近該初始容量的2的次方,并使用計(jì)算后的值來初始化數(shù)組對(duì)象,當(dāng)初始化完成數(shù)組對(duì)象時(shí)則會(huì)計(jì)算下一次數(shù)組擴(kuò)容的閾值。
在initTable
方法中我們看到代碼中使用一個(gè)變量sizeCtl
來區(qū)分多種情況,其實(shí)sizeCtl
一共分為4種情況,那我們先了解一下分為那4種。
sizeCtl = N && table = null
:當(dāng)數(shù)組還未被初始化的時(shí)候,sizeCtl
則是數(shù)組初始化的容量大小sizeCtl = N
:當(dāng)數(shù)組已經(jīng)被初始化了的時(shí)候,sizeCtl
則是數(shù)組下一次擴(kuò)容的閾值sizeCtl = -1
:說明當(dāng)前數(shù)組對(duì)象正在被初始化sizeCtl = -N
:當(dāng)前數(shù)組正在進(jìn)行擴(kuò)容,-N
的低16位二進(jìn)制轉(zhuǎn)換為十進(jìn)制數(shù)M
,M-1
則是當(dāng)前正在進(jìn)行擴(kuò)容的線程數(shù)量,-N
的高16位二進(jìn)制則是本次擴(kuò)容的標(biāo)記
2.通過spread
方法計(jì)算后的hash
值與數(shù)組索引長度進(jìn)行與運(yùn)算獲取到當(dāng)前元素節(jié)點(diǎn)所在的索引位置,并調(diào)用tabAt
方法根據(jù)索引位置計(jì)算該索引中的元素節(jié)點(diǎn)在內(nèi)存中的偏移量,再根據(jù)偏移量從table
數(shù)組中獲取元素節(jié)點(diǎn),如果該元素節(jié)點(diǎn)為空則根據(jù)指定的key
和value
創(chuàng)建一個(gè)新的元素節(jié)點(diǎn)并調(diào)用casTabAt
方法根據(jù)cas
操作將新的元素節(jié)點(diǎn)添加到數(shù)組中指定的索引位置上。
static { try { U = sun.misc.Unsafe.getUnsafe(); Class<?> k = ConcurrentHashMap.class; SIZECTL = U.objectFieldOffset (k.getDeclaredField("sizeCtl")); TRANSFERINDEX = U.objectFieldOffset (k.getDeclaredField("transferIndex")); BASECOUNT = U.objectFieldOffset (k.getDeclaredField("baseCount")); CELLSBUSY = U.objectFieldOffset (k.getDeclaredField("cellsBusy")); Class<?> ck = CounterCell.class; CELLVALUE = U.objectFieldOffset (ck.getDeclaredField("value")); Class<?> ak = Node[].class; //獲取數(shù)組中第一個(gè)元素在內(nèi)存中開始的偏移量 ABASE = U.arrayBaseOffset(ak); //獲取數(shù)組中元素在內(nèi)存中的增量地址 int scale = U.arrayIndexScale(ak); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); //獲取增量地址在32位二進(jìn)制情況下高位連續(xù)包含0的個(gè)數(shù) //使用31減去包含0的個(gè)數(shù)獲取到需要位移的次數(shù) ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); } catch (Exception e) { throw new Error(e); }
在看tabAt
方法和casTabAt
方法之前,我們先了解一下ConcurrentHashMap
中靜態(tài)代碼塊中的一些代碼,通過getUnsafe()
方法獲取到Unsafe
類,Unsafe
類是一個(gè)不安全的類,能通過該類直接對(duì)內(nèi)存中的一些資源進(jìn)行操作,U.objectFieldOffset
方法能獲取到指定名稱變量在內(nèi)存中的偏移量,后續(xù)能根據(jù)該偏移量直接對(duì)變量進(jìn)行操作,U.arrayBaseOffset
方法獲取指定類型數(shù)組中第一個(gè)元素在內(nèi)存中的偏移量,ABASE
則是數(shù)組中第一個(gè)元素在內(nèi)存中開始的偏移量,U.arrayIndexScale
方法獲取指定類型數(shù)組中元素在內(nèi)存中的增量地址,ASHIFT
則是通過增量地址在32位二進(jìn)制的情況下高位連續(xù)為0的個(gè)數(shù),并使用31減去連續(xù)為0的個(gè)數(shù)獲取到需要位移的次數(shù)。
tabAt
static final <K, V> Node<K, V> tabAt(Node<K, V>[] tab, int i) { //根據(jù)指定的索引位置計(jì)算該索引中的節(jié)點(diǎn)在內(nèi)存中的偏移量 //并從tab數(shù)組中根據(jù)索引所在的內(nèi)存的偏移量獲取節(jié)點(diǎn) return (Node<K, V>) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE); }
i << ASHIFT
獲取到第一個(gè)索引位置到索引i
的增量地址,再加上ABASE
即可獲取到索引i在內(nèi)存中的偏移量,然后在根據(jù)偏移量從volatile
類型的tab
數(shù)組中獲取節(jié)點(diǎn)。
casTabAt
static final <K, V> boolean casTabAt(Node<K, V>[] tab, int i,Node<K, V> c, Node<K, V> v) { //根據(jù)指定的索引位置計(jì)算該索引中的節(jié)點(diǎn)在內(nèi)存中的偏移量 //并根據(jù)偏移量獲取節(jié)點(diǎn),并將獲取到的節(jié)點(diǎn)與傳遞的節(jié)點(diǎn)c進(jìn)行比較 //如果兩個(gè)節(jié)點(diǎn)相同則使用節(jié)點(diǎn)v進(jìn)行替換 return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v); }
根據(jù)指定的索引位置的內(nèi)存偏移量獲取節(jié)點(diǎn),如果獲取到的節(jié)點(diǎn)與傳遞的節(jié)點(diǎn)c
相同則使用節(jié)點(diǎn)v進(jìn)行替換,該方法用于添加一個(gè)新的節(jié)點(diǎn)或替換節(jié)點(diǎn),添加新的節(jié)點(diǎn)的時(shí)候,只需要將節(jié)點(diǎn)c
設(shè)置為null
,替換節(jié)點(diǎn)的時(shí)候,節(jié)點(diǎn)c
則需要設(shè)置為原索引位置上的節(jié)點(diǎn)。
3.當(dāng)指定索引位置上的節(jié)點(diǎn)不為null
的時(shí)候,但是節(jié)點(diǎn)的hash
值為-1,此時(shí)說明有其它線程正在對(duì)數(shù)組進(jìn)行擴(kuò)容,并且指定索引位置上的節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移到了新的數(shù)組中,此時(shí)當(dāng)前線程就需要調(diào)用helpTransfer
方法協(xié)助其它線程進(jìn)行擴(kuò)容,當(dāng)協(xié)助完成時(shí)則會(huì)對(duì)新的數(shù)組進(jìn)行操作。
helpTransfer
final Node<K, V>[] helpTransfer(Node<K, V>[] tab, Node<K, V> f) { Node<K, V>[] nextTab; int sc; //f instanceof ForwardingNode 當(dāng)前節(jié)點(diǎn)正在轉(zhuǎn)移 //nextTab = ((ForwardingNode<K, V>) f).nextTable) != null 擴(kuò)容的新數(shù)組是否不為空 //如果條件都為true則說明有線程正在擴(kuò)容中,當(dāng)前線程則需要協(xié)助擴(kuò)容 if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K, V>) f).nextTable) != null) { //擴(kuò)容標(biāo)記 int rs = resizeStamp(tab.length); while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { //(sc >>> RESIZE_STAMP_SHIFT) != rs 校驗(yàn)擴(kuò)容標(biāo)識(shí)是否一致,不一致則說明不是同一個(gè)數(shù)組 //sc == rs + 1 應(yīng)該是sc == (rs << 16) + 1 校驗(yàn)擴(kuò)容是否完成 //sc == rs + MAX_RESIZERS 應(yīng)該是sc == (rs << 16) + MAX_RESIZERS 校驗(yàn)擴(kuò)容的線程的數(shù)量是否到達(dá)最大 //transferIndex <= 0 舊數(shù)組中還有多少?zèng)]有轉(zhuǎn)移的索引節(jié)點(diǎn)長度,如果小于等于0則說明已經(jīng)轉(zhuǎn)移完成則不需要協(xié)助 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; //擴(kuò)容線程加1 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { //協(xié)助擴(kuò)容 transfer(tab, nextTab); break; } } //擴(kuò)容完成則返回新的數(shù)組,對(duì)新的數(shù)組進(jìn)行操作 return nextTab; } //當(dāng)前節(jié)點(diǎn)還未被轉(zhuǎn)移或新的數(shù)組還沒有被初始化則對(duì)舊數(shù)組進(jìn)行操作 return table; }
我們來看一下協(xié)助擴(kuò)容的方法,局部變量nextTab
和sc
則是最新的擴(kuò)容數(shù)組和擴(kuò)容標(biāo)識(shí)以及擴(kuò)容的線程數(shù)量。
tab != null和f instanceof FowardingNode
:該條件只是為了代碼的健壯性,tab !=null
從代碼來看,既然當(dāng)前線程執(zhí)行到了協(xié)助擴(kuò)容的方法,那tab
肯定是不為null
的,再看f instanceof FowardingNode
條件,在之前的判斷中f的節(jié)點(diǎn)的hash值為-1
,-1就已經(jīng)代表節(jié)點(diǎn)已經(jīng)被轉(zhuǎn)移
,只有當(dāng)節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移的時(shí)候才會(huì)執(zhí)行到當(dāng)前方法中,此時(shí)你就可以發(fā)現(xiàn)之前為什么要將節(jié)點(diǎn)的hash
值控制在正整數(shù),如果說沒有控制在正整數(shù)的時(shí)候,當(dāng)節(jié)點(diǎn)的hash值恰巧為-1
的時(shí)候,就會(huì)導(dǎo)致線程執(zhí)行一些沒有必要的代碼,而且在后續(xù)也不好區(qū)分鏈表節(jié)點(diǎn)。(nextTab = ((ForwardingNode<K, V>) f).nextTable) != null
:該條件則是獲取最新的擴(kuò)容數(shù)組并校驗(yàn)最新的數(shù)組是否不為null
。
當(dāng)上述條件都為true
的時(shí)候則會(huì)通過resizeStamp
方法使用舊數(shù)組的長度生成一個(gè)擴(kuò)容標(biāo)識(shí),再看while
循環(huán)中的條件語句。
nextTab == nextTable
:校驗(yàn)當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組是否是最新的數(shù)組,如果此時(shí)nextTable == null
的話則說明已經(jīng)完成了擴(kuò)容,那當(dāng)前線程則不需要協(xié)助擴(kuò)容了,只需要執(zhí)行自己該執(zhí)行的操作,如果說當(dāng)前線程協(xié)助擴(kuò)容的數(shù)組不是最新的擴(kuò)容數(shù)組的時(shí)候,則會(huì)從nextTable
中繼續(xù)獲取f
節(jié)點(diǎn),再從f
節(jié)點(diǎn)中獲取nextTable
,繼續(xù)校驗(yàn)是否是最新的擴(kuò)容數(shù)組。
table == tab
:校驗(yàn)擴(kuò)容是否完成。
(sc = sizeCtl) < 0
:校驗(yàn)是否正在擴(kuò)容中。
上述條件都為true
的時(shí)候則說明數(shù)組正在進(jìn)行擴(kuò)容中,我們先來看一些在哪些條件下當(dāng)前線程不會(huì)去協(xié)助擴(kuò)容。
(sc >>> RESIZE_STAMP_SHIFT) != rs
:校驗(yàn)擴(kuò)容標(biāo)識(shí)是否一致,不一致則說明不是對(duì)同一長度的數(shù)組進(jìn)行的擴(kuò)容。
sc == rs + 1
:該條件應(yīng)該是sc == (rs << 16) + 1
,校驗(yàn)擴(kuò)容是否完成,在擴(kuò)容開始的時(shí)候sizeCtl為(rs << 16) + 2
,這個(gè)2代表著開始擴(kuò)容的線程以及最后一個(gè)擴(kuò)容完成的線程,在transfer
擴(kuò)容方法中,每一個(gè)線程完成了所否則的區(qū)域的時(shí)候都會(huì)將sizeCtl
的值減1,當(dāng)最后一個(gè)線程執(zhí)行完成時(shí)減1,此時(shí)sizeCtl
的值就等于(rs<< 16) + 1
則說明整個(gè)數(shù)組擴(kuò)容都已經(jīng)完成了,已經(jīng)不需要線程協(xié)助了。
sc == rs + MAX_RESIZERS
:該條件應(yīng)該是sc == (rs << 16) + MAX_RESIZERS
,校驗(yàn)當(dāng)前正在擴(kuò)容的線程的數(shù)量是否到達(dá)最大值(65535)。
transferIndex <= 0
:校驗(yàn)舊數(shù)組中還未完成轉(zhuǎn)移的索引節(jié)點(diǎn)長度是否小于等于0,小于等于0則說明已經(jīng)完成了轉(zhuǎn)移則不需要協(xié)助。
當(dāng)上述條件都不成立的時(shí)候則說明數(shù)組擴(kuò)容需要協(xié)助,此時(shí)通過cas
操作將擴(kuò)容線程的數(shù)量加1并調(diào)用transfer
方法來協(xié)助擴(kuò)容,該擴(kuò)容方法放在后面講解。
4.當(dāng)上面3個(gè)分支都不成立的時(shí)候,則說明該索引位置上的節(jié)點(diǎn)不為空并且沒有被轉(zhuǎn)移,此時(shí)就需要使用synchronized
對(duì)指定索引位置上的節(jié)點(diǎn)加鎖,然后根據(jù)節(jié)點(diǎn)的類型來執(zhí)行相關(guān)的操作。
- 鏈表節(jié)點(diǎn):鏈表節(jié)點(diǎn)的
hash
值都是大于等于0的,當(dāng)指定索引位置上的節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn)的時(shí)候,則會(huì)從鏈表的頭節(jié)點(diǎn)開始遍歷并且記錄鏈表中節(jié)點(diǎn)的數(shù)量,當(dāng)鏈表中的節(jié)點(diǎn)的key
與指定的key
相同則根據(jù)onlyIfAbsent
參數(shù)來決定是否需要替換value
,如果沒有相同的key
時(shí),則會(huì)為指定的key
和value
創(chuàng)建一個(gè)新的節(jié)點(diǎn),并將新的節(jié)點(diǎn)添加到鏈表的尾部。 - 紅黑樹節(jié)點(diǎn):紅黑樹的頭節(jié)點(diǎn)的
hash
值固定為-2,此處直接校驗(yàn)的是頭節(jié)點(diǎn)的類型是否是紅黑樹,當(dāng)指定索引位置上的頭節(jié)點(diǎn)是一個(gè)紅黑樹節(jié)點(diǎn),則會(huì)調(diào)用頭節(jié)點(diǎn)的putTreeVal
方法將指定的key
和value
添加到紅黑樹中,如果說指定的key
已經(jīng)存在在紅黑樹中則會(huì)根據(jù)onlyIfAbsent
參數(shù)來決定是否需要替換value
,此處為什么是調(diào)用頭節(jié)點(diǎn)的putTreeVal
方法呢?看到后面鏈表轉(zhuǎn)換為紅黑樹節(jié)點(diǎn)的時(shí)候就能明白了。
putTreeVal
final TreeNode<K, V> putTreeVal(int h, K k, V v) { Class<?> kc = null; boolean searched = false; for (TreeNode<K, V> p = root; ; ) { int dir, ph; K pk; if (p == null) { first = root = new TreeNode<K, V>(h, k, v, null, null); break; } else if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((pk = p.key) == k || (pk != null && k.equals(pk))) return p; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) { if (!searched) { TreeNode<K, V> q, ch; searched = true; if (((ch = p.left) != null && (q = ch.findTreeNode(h, k, kc)) != null) || ((ch = p.right) != null && (q = ch.findTreeNode(h, k, kc)) != null)) return q; } dir = tieBreakOrder(k, pk); } TreeNode<K, V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { TreeNode<K, V> x, f = first; first = x = new TreeNode<K, V>(h, k, v, f, xp); if (f != null) //將新添加的樹節(jié)點(diǎn)設(shè)置為鏈表的頭節(jié)點(diǎn) f.prev = x; if (dir <= 0) xp.left = x; else xp.right = x; if (!xp.red) x.red = true; else { //獲取寫鎖,如果獲取寫鎖失敗則會(huì)將當(dāng)前線程掛起進(jìn)行等待 //如果有線程正在讀,那當(dāng)前寫的線程就要掛起進(jìn)行等待 //當(dāng)讀的線程執(zhí)行完成之后就會(huì)去喚醒掛起的線程 //如果說當(dāng)前線程在寫,有線程準(zhǔn)備讀 //那讀線程并不會(huì)去讀取紅黑樹中的節(jié)點(diǎn) //而是讀取鏈表的節(jié)點(diǎn),因?yàn)門reeBin對(duì)象中包含了紅黑樹的根節(jié)點(diǎn)并且也包含了鏈表的頭節(jié)點(diǎn) //如果發(fā)生寫寫的問題呢? //其實(shí)并不會(huì)發(fā)生寫寫的問題,因?yàn)楫?dāng)前整個(gè)方法都被外部的synchronized鎖住了 lockRoot(); try { //平衡紅黑樹中的節(jié)點(diǎn) root = balanceInsertion(root, x); } finally { //釋放鎖 unlockRoot(); } } break; } } assert checkInvariants(root); return null; }
putTreeVal
和balanceInsertion
方法在之前的HashMap
中已經(jīng)講過,此處就不再講解,我們主要還是看一下lockRoot
和unlockRoot
方法。
鎖狀態(tài)
//鎖狀態(tài) volatile int lockState; //寫鎖 static final int WRITER = 1; // set while holding write lock //等待 static final int WAITER = 2; // set when waiting for write lock //讀鎖 static final int READER = 4; // increment value for setting read lock
lockRoot
private final void lockRoot() { //嘗試加寫鎖 if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) //加寫鎖失敗則嘗試將線程掛起進(jìn)行等待 contendedLock(); }
通過cas
操作嘗試加寫鎖,當(dāng)加寫鎖失敗則會(huì)調(diào)用contendedLock
方法將線程掛起進(jìn)行等待。
contendedLock
private final void contendedLock() { boolean waiting = false; for (int s; ; ) { /** * ~WAITER = 1111 1111 1111 1111 1111 1111 1111 1101 * ((s = lockState) & ~WAITER) == 0 等于0則說明當(dāng)前沒有線程對(duì)當(dāng)前TreeBin對(duì)象下的紅黑樹進(jìn)行加鎖 * 不等于則說明有線程對(duì)TreeBin對(duì)象下的紅黑樹加鎖 */ if (((s = lockState) & ~WAITER) == 0) { //沒有線程對(duì)TreeBin對(duì)象下的紅黑樹加鎖 //當(dāng)前線程則可以對(duì)TreeBin對(duì)象下的紅黑樹嘗試加鎖 if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { if (waiting) //將等待線程清空 waiter = null; return; } //(s & WAITER) == 0 執(zhí)行當(dāng)前判斷語句的時(shí)候則說明有線程對(duì)紅黑樹加了鎖 //如果(s & WAITER)等于0則說明沒有線程在等待加鎖 //此時(shí)當(dāng)前線程就可以進(jìn)行等待 } else if ((s & WAITER) == 0) { //將鎖狀態(tài)的32位二進(jìn)制中的第2位設(shè)置為1 //代表當(dāng)前有一個(gè)線程正在等待加鎖 if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { waiting = true; //將TreeBin中的等待線程設(shè)置為當(dāng)前線程 waiter = Thread.currentThread(); } } else if (waiting) //當(dāng)前線程已經(jīng)設(shè)置成了等待狀態(tài)了,此時(shí)就需要將線程掛起 LockSupport.park(this); } }
contendedLock
方法中總共有3個(gè)分支,我們就一個(gè)一個(gè)的開始講解。
((s = lockState) & ~WAITER) == 0
:校驗(yàn)是否有線程對(duì)指定索引位置上的TreeBin
對(duì)象中的紅黑樹進(jìn)行加鎖。
~WAITER = -3
,32位二進(jìn)制為 1111 1111 1111 1111 1111 1111 1111 1101
WRITER = 1
,1為寫鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0001
READER = 4
,4為讀鎖,32位二進(jìn)制為 0000 0000 0000 0000 0000 0000 0000 0100
使用最新的鎖狀態(tài)與~WAITER
進(jìn)行與運(yùn)算,當(dāng)加了讀鎖或?qū)戞i時(shí),讀鎖和寫鎖的二進(jìn)制標(biāo)識(shí)與~WAITER
二進(jìn)制標(biāo)識(shí)都為1的情況下則說明已經(jīng)有線程加了鎖,如果有線程加了鎖,那當(dāng)前線程則會(huì)執(zhí)行后續(xù)的代碼來將自己掛起并等待。
(s & WAITER) == 0
:執(zhí)行到當(dāng)前語句的時(shí)候就說明已經(jīng)有線程加了鎖,此時(shí)就需要校驗(yàn)一下是否有線程掛起并在等待,從代碼上來看,并不會(huì)出現(xiàn)多個(gè)線程同時(shí)等待,只會(huì)存在一個(gè)等待的線程,如果沒有線程在等待,則會(huì)通過cas
操作將鎖狀態(tài)加上一個(gè)有線程在等待的標(biāo)識(shí),并將TreeBin
對(duì)象中的等待線程設(shè)置為當(dāng)前線程。
waiting
:當(dāng)有線程加了鎖,并且將當(dāng)前線程設(shè)置為了等待的線程,此時(shí)就需要將當(dāng)前線程掛起。
為什么說只會(huì)存在一個(gè)等待的線程呢?
讀鎖與寫鎖本就互斥,在get
方法中,獲取紅黑樹中的節(jié)點(diǎn)的時(shí)候則會(huì)加上一個(gè)讀鎖,此時(shí)寫鎖就需要掛起進(jìn)行等待,當(dāng)讀鎖釋放完成之后就會(huì)喚醒加寫鎖的線程,如果已經(jīng)有線程加了寫鎖,那get
方法則不會(huì)從紅黑樹中獲取節(jié)點(diǎn),而是從TreeBin
對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷進(jìn)行匹配,那會(huì)不會(huì)發(fā)生多個(gè)線程同時(shí)去加寫鎖呢?其實(shí)并不會(huì),因?yàn)榧訉戞i的方法的外部整個(gè)都被synchronized
鎖住了,所以并不會(huì)存在多個(gè)線程同時(shí)加寫鎖,也不會(huì)存在多個(gè)等待的線程。
鏈表添加節(jié)點(diǎn)和紅黑樹添加節(jié)點(diǎn)都已經(jīng)講解完畢,此時(shí)就要看一下后續(xù)代碼,鏈表節(jié)點(diǎn)在什么情況下會(huì)轉(zhuǎn)換為紅黑樹以及是怎么轉(zhuǎn)換的。
if (binCount != 0) { //校驗(yàn)索引位置上的節(jié)點(diǎn)的數(shù)量是否到達(dá)樹化的閾值 //如果該索引位置上的節(jié)點(diǎn)本來就是樹節(jié)點(diǎn)則不會(huì)繼續(xù)樹化 //只有當(dāng)索引位置上的節(jié)點(diǎn)是鏈表節(jié)點(diǎn)并且節(jié)點(diǎn)的數(shù)量大于等于8的時(shí)候才會(huì)樹化 if (binCount >= TREEIFY_THRESHOLD) //索引位置上的節(jié)點(diǎn)數(shù)量已經(jīng)到達(dá)樹化的閾值 //則將該索引位置上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn) treeifyBin(tab, i); if (oldVal != null) //返回被替換的舊值 return oldVal; break; }
在添加一個(gè)節(jié)點(diǎn)到鏈表中去,會(huì)從鏈表的頭節(jié)點(diǎn)開始遍歷并記錄鏈表中的節(jié)點(diǎn)數(shù)量,該數(shù)量就是用binCount
記錄的,當(dāng)鏈表中的節(jié)點(diǎn)數(shù)量大于等于TREEIFY_THRESHOLD(8)
的時(shí)候則會(huì)調(diào)用treeifyBin
方法來決定是否需要將鏈表中的所有節(jié)點(diǎn)樹化并轉(zhuǎn)換為紅黑樹。
treeifyBin
private final void treeifyBin(Node<K, V>[] tab, int index) { //指定索引位置上的節(jié)點(diǎn) Node<K, V> b; //n 數(shù)組長度 int n, sc; if (tab != null) { //校驗(yàn)數(shù)組的長度是否小于最小的樹化閾值 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) /** * 數(shù)組長度小于最小的樹化閾值 * 此時(shí)并不會(huì)將數(shù)組中的鏈表節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn) * 只會(huì)將數(shù)組進(jìn)行擴(kuò)容 * * 如果仔細(xì)查看tryPresize方法你會(huì)發(fā)現(xiàn)該方法中也對(duì)n進(jìn)行了左移1位 * 當(dāng)前tryPresize傳參的時(shí)候就已經(jīng)對(duì)n進(jìn)行了左移1位,然后在方法里面又對(duì)n進(jìn)行了左移1位 * 這樣是不是就對(duì)數(shù)組進(jìn)行了4倍擴(kuò)容,并不是我們想的那樣2倍擴(kuò)容呢? * 其實(shí)不是的,在tryPresize方法里面并沒有使用2次位移后的n來擴(kuò)容 * 而是使用最開始的數(shù)組長度n來計(jì)算進(jìn)行擴(kuò)容 */ tryPresize(n << 1); //獲取指定索引位置上的元素節(jié)點(diǎn)并校驗(yàn)元素節(jié)點(diǎn)是否不為空 //如果元素節(jié)點(diǎn)不為空則校驗(yàn)該節(jié)點(diǎn)的hash值是大于等于0 //如果hash值大于等于0則說明該節(jié)點(diǎn)需要轉(zhuǎn)換為樹節(jié)點(diǎn) //如果hash值小于0則說明該節(jié)點(diǎn)正在移動(dòng)或已經(jīng)被樹化了 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { //使用鎖鎖住指定索引位置上的節(jié)點(diǎn) //在添加元素的時(shí)候也會(huì)對(duì)指定索引位置上的節(jié)點(diǎn)加鎖 //如果兩個(gè)索引位置都是相同的索引位置的時(shí)候,如果添加元素的線程先加了鎖 //那執(zhí)行到當(dāng)前方法的線程則會(huì)等待添加元素的線程釋放鎖 //當(dāng)添加元素的線程釋放了鎖之后,當(dāng)前方法的線程則會(huì)獲取鎖將索引位置上所有的節(jié)點(diǎn)都樹化,包括新添加的元素節(jié)點(diǎn) //如果是執(zhí)行到當(dāng)前方法的線程獲取到了鎖,那添加元素的線程則會(huì)等待 //當(dāng)指定索引位置上的所有節(jié)點(diǎn)都樹化了之后并釋放了鎖 //添加元素的線程則會(huì)去獲取鎖,此時(shí)添加元素的線程會(huì)發(fā)現(xiàn)當(dāng)前索引位置上的節(jié)點(diǎn)已經(jīng)是樹節(jié)點(diǎn) //則會(huì)將元素節(jié)點(diǎn)添加到紅黑樹中并使紅黑樹平衡 synchronized (b) { //校驗(yàn)在獲取鎖的時(shí)候指定索引位置上的節(jié)點(diǎn)是否被更改 if (tabAt(tab, index) == b) { //hd 頭節(jié)點(diǎn) //tl 當(dāng)前遍歷到的節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn) TreeNode<K, V> hd = null, tl = null; //從指定索引位置上的節(jié)點(diǎn)開始遍歷,依次將鏈表上的所有節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn) for (Node<K, V> e = b; e != null; e = e.next) { //將普通節(jié)點(diǎn)轉(zhuǎn)換為樹節(jié)點(diǎn) TreeNode<K, V> p = new TreeNode<K, V>(e.hash, e.key, e.val, null, null); //校驗(yàn)當(dāng)前遍歷的節(jié)點(diǎn)是否是鏈表中的頭節(jié)點(diǎn) //如果是頭節(jié)點(diǎn)則將該節(jié)點(diǎn)設(shè)置為了樹節(jié)點(diǎn)的頭節(jié)點(diǎn) //如果不是頭節(jié)點(diǎn)則將當(dāng)前轉(zhuǎn)換的樹節(jié)點(diǎn)與上一個(gè)樹節(jié)點(diǎn)進(jìn)行關(guān)聯(lián) if ((p.prev = tl) == null) hd = p; else tl.next = p; tl = p; } //創(chuàng)建一個(gè)TreeBin對(duì)象并將樹化的節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹 //TreeBin對(duì)象中保留著紅黑樹的根節(jié)點(diǎn)以及鏈表的頭節(jié)點(diǎn) //再調(diào)用setTabAt方法將TreeBin對(duì)象添加到指定的索引位置上 //在HashMap中并沒有創(chuàng)建一個(gè)TreeBin對(duì)象來存放紅黑樹的根節(jié)點(diǎn) //而是直接將紅黑樹的根節(jié)點(diǎn)放置到指定的索引位置上 setTabAt(tab, index, new TreeBin<K, V>(hd)); } } } } }
(n = tab.length) < MIN_TREEIFY_CAPACITY
:校驗(yàn)數(shù)組的長度是否小于最小樹化的閾值,并不是說鏈表中的節(jié)點(diǎn)數(shù)量到達(dá)8了就要將鏈表節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹節(jié)點(diǎn),前提是需要數(shù)組的長度到達(dá)了閾值(64)才會(huì)轉(zhuǎn)換為紅黑樹節(jié)點(diǎn),如果數(shù)組的長度沒有到達(dá)閾值則會(huì)進(jìn)行擴(kuò)容。
(b = tabAt(tab, index)) != null && b.hash >= 0
:校驗(yàn)外部synchronized
釋放鎖之后,準(zhǔn)備節(jié)點(diǎn)樹化的期間是否有其它線程對(duì)該節(jié)點(diǎn)進(jìn)行操作了,該操作分為兩種,有線程將該索引位置上的所有節(jié)點(diǎn)都刪除了或者說有線程將該索引位置上的節(jié)點(diǎn)都樹化了,這兩種情況下就不需要再進(jìn)行樹化了。 我們主要看一下是怎么樹化的以及轉(zhuǎn)換成紅黑樹的,樹化的操作其實(shí)很簡(jiǎn)單,就是對(duì)指定索引位置上的節(jié)點(diǎn)加鎖防止其它線程操作,依次從鏈表的頭節(jié)點(diǎn)開始遍歷,將鏈表中的Node
節(jié)點(diǎn)轉(zhuǎn)換為TreeNode
節(jié)點(diǎn),就這樣樹化就完成了,而轉(zhuǎn)換為紅黑樹就是通過創(chuàng)建一個(gè)TreeBin
對(duì)象來完成的,在構(gòu)造TreeBin
對(duì)象時(shí)會(huì)將樹化的節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹中的節(jié)點(diǎn)。
TreeBin
TreeBin(TreeNode<K, V> b) { //給當(dāng)前TreeBin對(duì)象設(shè)置一個(gè)樹化標(biāo)識(shí) super(TREEBIN, null, null, null); //樹化的頭節(jié)點(diǎn)的設(shè)置為鏈表的頭節(jié)點(diǎn) this.first = b; //根節(jié)點(diǎn) TreeNode<K, V> r = null; //從樹化的頭節(jié)點(diǎn)開始遍歷將節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹中的節(jié)點(diǎn),直到?jīng)]有下一個(gè)節(jié)點(diǎn) for (TreeNode<K, V> x = b, next; x != null; x = next) { //下一個(gè)節(jié)點(diǎn) next = (TreeNode<K, V>) x.next; //初始化每個(gè)樹節(jié)點(diǎn)的左右子節(jié)點(diǎn) x.left = x.right = null; if (r == null) { //根節(jié)點(diǎn)為空則將當(dāng)前遍歷到的節(jié)點(diǎn)設(shè)置為根節(jié)點(diǎn) //并將該節(jié)點(diǎn)的顏色設(shè)置為黑色 x.parent = null; x.red = false; r = x; } else { //待添加到紅黑樹中的節(jié)點(diǎn)的key K k = x.key; //待添加到紅黑樹中的節(jié)點(diǎn)的hash值 int h = x.hash; Class<?> kc = null; //從紅黑樹的根節(jié)點(diǎn)開始遍歷來校驗(yàn)節(jié)點(diǎn)放置的位置 for (TreeNode<K, V> p = r; ; ) { //dir 節(jié)點(diǎn)添加到紅黑樹中的位置 //ph 被遍歷到的紅黑樹中的節(jié)點(diǎn)的key int dir, ph; //被遍歷到的紅黑樹中的節(jié)點(diǎn)的key K pk = p.key; if ((ph = p.hash) > h) //被遍歷到的紅黑樹中的節(jié)點(diǎn)的hash值大于待添加到紅黑樹中的節(jié)點(diǎn)的hash值 //則需要將節(jié)點(diǎn)添加到紅黑樹中的左子節(jié)點(diǎn) dir = -1; else if (ph < h) //被遍歷到的紅黑樹中的節(jié)點(diǎn)的hash值小于待添加到紅黑樹中的節(jié)點(diǎn)的hash值 //則需要將節(jié)點(diǎn)添加到紅黑樹中的右子節(jié)點(diǎn) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K, V> xp = p; //校驗(yàn)待添加到紅黑樹中的節(jié)點(diǎn)所放置的位置是否有節(jié)點(diǎn)存在 //如果有節(jié)點(diǎn)存在則重新走循環(huán)與子節(jié)點(diǎn)繼續(xù)比較直到?jīng)]有了子節(jié)點(diǎn) if ((p = (dir <= 0) ? p.left : p.right) == null) { //放置待添加到紅黑樹中的節(jié)點(diǎn)的位置沒有了節(jié)點(diǎn) //則會(huì)將節(jié)點(diǎn)添加到紅黑樹中 //將待添加節(jié)點(diǎn)的父節(jié)點(diǎn)的指針指向當(dāng)前遍歷到的紅黑樹中的節(jié)點(diǎn) x.parent = xp; if (dir <= 0) //將待添加的節(jié)點(diǎn)放置到被遍歷到的節(jié)點(diǎn)的左子節(jié)點(diǎn) xp.left = x; else //將待添加的節(jié)點(diǎn)放置到被遍歷到的節(jié)點(diǎn)的右子節(jié)點(diǎn) xp.right = x; //紅黑樹中添加了新的節(jié)點(diǎn),可能讓紅黑樹不在平衡 //所以需要將紅黑樹中的節(jié)點(diǎn)進(jìn)行平衡 r = balanceInsertion(r, x); break; } } } } //TreeBin對(duì)象記錄根節(jié)點(diǎn) this.root = r; assert checkInvariants(root); }
TreeBin
的構(gòu)造方法比較簡(jiǎn)單,通過super
方法給當(dāng)前的TreeBin
對(duì)象設(shè)置hash值為-2
,然后通過遍歷樹化后的鏈表節(jié)點(diǎn),將鏈表的頭節(jié)點(diǎn)設(shè)置為紅黑樹的根節(jié)點(diǎn),后續(xù)鏈表中的節(jié)點(diǎn)從紅黑樹的根節(jié)點(diǎn)開始進(jìn)行比較,來獲取到鏈表的節(jié)點(diǎn)在紅黑樹中所存放的位置,紅黑樹按左小右大的方式存放節(jié)點(diǎn),每添加一個(gè)節(jié)點(diǎn)到紅黑樹中,都有可能讓紅黑樹不平衡,所以每次都會(huì)嘗試是否需要平衡紅黑樹中的節(jié)點(diǎn)。
上圖是鏈表轉(zhuǎn)紅黑樹,轉(zhuǎn)為紅黑樹之后,索引位置上是一個(gè)TreeBin
對(duì)象,并且TreeBin
對(duì)象中包含了紅黑樹的根節(jié)點(diǎn)以及鏈表的頭節(jié)點(diǎn),而紅黑樹中的節(jié)點(diǎn)中不僅有紅黑樹本身的指針,而且還有鏈表的一些指針,既能當(dāng)做是紅黑樹也可以當(dāng)做鏈表,上圖中并沒有將所有節(jié)點(diǎn)的鏈表指針都畫出來,只是畫了部分節(jié)點(diǎn)的鏈表指針。
當(dāng)元素添加操作以及鏈表轉(zhuǎn)換為紅黑樹操作完成之后,我們?cè)賮砜?code>ConcurrentHashMap是如何記錄元素?cái)?shù)量的以及擴(kuò)容的。
addCount
/** * 添加元素節(jié)點(diǎn)或刪除元素節(jié)點(diǎn)的時(shí)候需要將計(jì)數(shù)器中的值修改 * 如果在單線程的情況下直接對(duì)基本計(jì)數(shù)器值修改 * 如果在多線程的情況下對(duì)基本計(jì)數(shù)器修改失敗的話并且計(jì)數(shù)器數(shù)組為空 * 則需要初始化計(jì)數(shù)器數(shù)組,并使用線程生成隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組的長度進(jìn)行運(yùn)算獲取到指定的索引位置 * 并將指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化并將check值保存在計(jì)數(shù)器對(duì)象中的value * 如果計(jì)數(shù)器數(shù)組不為空,但是指定的索引位置上的計(jì)數(shù)器對(duì)象為空 * 則初始化計(jì)數(shù)器對(duì)象并將check值保存在計(jì)數(shù)器對(duì)象中的value * 如果計(jì)數(shù)器對(duì)象也不為空則直接將check值累加到計(jì)數(shù)器對(duì)象中的value * 在將check值保存之后則會(huì)校驗(yàn)當(dāng)前集合中的數(shù)組是否需要擴(kuò)容 * 如果需要擴(kuò)容則會(huì)調(diào)用transfer方法來進(jìn)行擴(kuò)容 */ private final void addCount(long x, int check) { //計(jì)數(shù)器數(shù)組 CounterCell[] as; //b 基本計(jì)數(shù)器值 long b, s; //計(jì)數(shù)器數(shù)組counterCells不為空則說明之前已經(jīng)發(fā)生過多線程對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作 //計(jì)數(shù)器數(shù)組counterCells為空則說明之前一直在單線程的情況下對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作 //如果計(jì)數(shù)器數(shù)組為空則會(huì)嘗試對(duì)基本計(jì)數(shù)器baseCount進(jìn)行操作,如果對(duì)baseCount操作失敗則說明當(dāng)前處于多線程的情況下 //此時(shí)就需要初始化counterCells if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { CounterCell a; long v; int m; boolean uncontended = true; //as == null || (m = as.length - 1) < 0 校驗(yàn)計(jì)數(shù)器數(shù)組是否初始化,如果沒有初始化則調(diào)用fullAddCount方法進(jìn)行初始化 //(a = as[ThreadLocalRandom.getProbe() & m]) == null 當(dāng)前計(jì)數(shù)器數(shù)組不為空的時(shí)候則使用線程生成的隨機(jī)數(shù) //與計(jì)數(shù)器數(shù)組的長度進(jìn)行與運(yùn)算獲取到指定索引位置上的計(jì)數(shù)器對(duì)象,并校驗(yàn)計(jì)數(shù)器對(duì)象是否為空 //如果計(jì)數(shù)器對(duì)象為空則調(diào)用fullAddCount方法對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化 //!(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) 計(jì)數(shù)器數(shù)組不為空并且運(yùn)算獲取到的指定索引位置上的計(jì)數(shù)器對(duì)象也不為空 //此時(shí)就可以對(duì)計(jì)數(shù)器對(duì)象進(jìn)行操作,如果操作失敗則說明有其它線程獲取到了這個(gè)索引位置上的計(jì)數(shù)器對(duì)象并對(duì)這個(gè)計(jì)數(shù)器進(jìn)行操作中 //此時(shí)就需要調(diào)用fullAddCount方法選擇其它索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作 if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { //對(duì)計(jì)數(shù)器數(shù)組以及計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行初始化 //一個(gè)線程對(duì)計(jì)數(shù)器對(duì)象操作失敗則會(huì)更換其它索引上的計(jì)數(shù)器對(duì)象進(jìn)行操作 //如果這個(gè)線程多次對(duì)計(jì)數(shù)器對(duì)象操作失敗并且當(dāng)前計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量 //此時(shí)就會(huì)嘗試進(jìn)行擴(kuò)容,然后對(duì)擴(kuò)容之后的數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作 //如果說計(jì)數(shù)器數(shù)組的長度不小于cpu的數(shù)量則不會(huì)進(jìn)行擴(kuò)容 //只會(huì)一直更換計(jì)數(shù)器對(duì)象進(jìn)行操作,直到操作成功 fullAddCount(x, uncontended); return; } //校驗(yàn)check是否小于等于1 //check=-1的情況下則說明刪除了指定索引位置上的元素節(jié)點(diǎn) //check=0的情況下則說明指定索引位置上沒有元素節(jié)點(diǎn),直接將指定的key和value封裝成一個(gè)節(jié)點(diǎn)放置到指定的索引位置上 //check=1的情況在put方法下則說明指定索引位置上有元素節(jié)點(diǎn),但是只是對(duì)索引位置上的元素節(jié)點(diǎn)中的value進(jìn)行了替換 //在其它方法中check=1可能表示在指定的索引位置上添加了1個(gè)元素節(jié)點(diǎn) //如果是這三種情況的話則不會(huì)去嘗試進(jìn)行擴(kuò)容 //如果執(zhí)行到了當(dāng)前判斷語句的話則說明并沒有執(zhí)行上面的fullAddCount方法 //而是通過上面判斷語句中的cas操作對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象的值操作成功 //但是在check=1的情況下的put方法只是將原本的元素節(jié)點(diǎn)中的value替換了 //如果此時(shí)還要對(duì)計(jì)數(shù)器對(duì)象中的value加1,那不就會(huì)造成1個(gè)元素節(jié)點(diǎn)變成了2個(gè)元素節(jié)點(diǎn)? //其實(shí)并不會(huì)的,如果check=1的情況下put方法并不會(huì)走到當(dāng)前的方法中 if (check <= 1) return; //將計(jì)數(shù)器數(shù)組中所有的計(jì)數(shù)器對(duì)象值以及基本的計(jì)數(shù)器值合并獲取到當(dāng)前集合中的元素節(jié)點(diǎn)的數(shù)量 s = sumCount(); } //校驗(yàn)check是否大于等于0 //大于等于0則說明添加了新的元素節(jié)點(diǎn) //此時(shí)就需要來校驗(yàn)是否需要進(jìn)行擴(kuò)容 //check一共分為5種情況,有三種已經(jīng)在上面說過,現(xiàn)在來看一下剩下的兩種情況 //check=2的情況下代表在紅黑樹種添加了一個(gè)樹節(jié)點(diǎn),也有可能是在一個(gè)鏈表節(jié)點(diǎn)中添加了一個(gè)節(jié)點(diǎn) //check>2的情況下代表在鏈表節(jié)點(diǎn)中添加了一個(gè)節(jié)點(diǎn) if (check >= 0) { Node<K, V>[] tab, nt; int n, sc; //當(dāng)前數(shù)組中的元素節(jié)點(diǎn)的數(shù)量已經(jīng)到達(dá)擴(kuò)容的閾值并且數(shù)組的長度小于數(shù)組最大容量大小 //此時(shí)會(huì)嘗試將數(shù)組進(jìn)行擴(kuò)容,如果擴(kuò)容失敗則會(huì)一直進(jìn)行嘗試,直到擴(kuò)容成功 while (s >= (long) (sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { //獲取擴(kuò)容時(shí)的標(biāo)記 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; //擴(kuò)容線程加1 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) //協(xié)助擴(kuò)容 transfer(tab, nt); //將sizeCtl修改成一個(gè)很大的負(fù)數(shù),告知其它線程現(xiàn)在有線程正在擴(kuò)容 //為什么要+2而不是+1?+1代表當(dāng)前擴(kuò)容的線程數(shù)量+1,而+2可能是標(biāo)識(shí)開啟擴(kuò)容 } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) //擴(kuò)容 transfer(tab, null); //重新計(jì)算集合中元素節(jié)點(diǎn)的數(shù)量 s = sumCount(); } } }
addCount
中分為兩個(gè)分支,分別為元素?cái)?shù)量計(jì)數(shù)以及數(shù)組擴(kuò)容,我們先看計(jì)數(shù)的分支。
在單線程的情況下只會(huì)對(duì)基本計(jì)數(shù)器baseCount
進(jìn)行操作,如果在多線程的情況下同時(shí)對(duì)baseCount
進(jìn)行操作,只會(huì)有一個(gè)線程操作成功,而其它線程并不會(huì)等待繼續(xù)對(duì)baseCount
進(jìn)行操作,而是調(diào)用fullAddCount
方法初始化計(jì)數(shù)器數(shù)組(CounterCells
)以及數(shù)組中的計(jì)數(shù)器對(duì)象(CounterCell
)并對(duì)數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作,只要出現(xiàn)過一次多線程的情況,往后都不會(huì)對(duì)baseCount
操作了,而是直接對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作,fullAddCount
中代碼具體的意思可以參考下面代碼中的注釋,這里不再多講。
fullAddCount
private final void fullAddCount(long x, boolean wasUncontended) { int h; //使用當(dāng)前線程生成隨機(jī)數(shù),如果隨機(jī)數(shù)為0則說明ThreadLocalRandom中的一些參數(shù)還未被初始化 //此時(shí)就需要進(jìn)行初始化并重新獲取隨機(jī)數(shù) if ((h = ThreadLocalRandom.getProbe()) == 0) { ThreadLocalRandom.localInit(); h = ThreadLocalRandom.getProbe(); wasUncontended = true; } boolean collide = false; for (; ; ) { //計(jì)數(shù)器數(shù)組 CounterCell[] as; //計(jì)數(shù)器對(duì)象 CounterCell a; //計(jì)數(shù)器數(shù)組長度 int n; //計(jì)數(shù)器對(duì)象中的value long v; //校驗(yàn)計(jì)數(shù)器數(shù)組是否已初始化 if ((as = counterCells) != null && (n = as.length) > 0) { //校驗(yàn)當(dāng)前線程生成的隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組索引長度運(yùn)算后獲取到的指定索引位置上的計(jì)數(shù)器對(duì)象是否為空 if ((a = as[(n - 1) & h]) == null) { //校驗(yàn)計(jì)數(shù)器數(shù)組的鎖狀態(tài)是否為0 //如果不為0則說明有其它線程正在對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容或?qū)τ?jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行初始化 if (cellsBusy == 0) { //創(chuàng)建一個(gè)計(jì)數(shù)器對(duì)象,并將值添加到計(jì)數(shù)器對(duì)象中 CounterCell r = new CounterCell(x); //加鎖,防止多個(gè)線程同時(shí)對(duì)通一個(gè)索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行初始化 if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean created = false; try { CounterCell[] rs; int m, j; //校驗(yàn)一下運(yùn)算后的索引位置上的計(jì)數(shù)器對(duì)象是否為空 //如果不為空則說明已經(jīng)被其它線程初始化了,當(dāng)前線程就不需要再去初始化 if ((rs = counterCells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { //將創(chuàng)建好的計(jì)數(shù)器對(duì)象添加到運(yùn)算后的指定索引位置上 rs[j] = r; //初始化成功 created = true; } } finally { //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí) cellsBusy = 0; } if (created) //初始化成功則退出 break; //初始化失敗,說明已經(jīng)有其它線程初始化了計(jì)數(shù)器對(duì)象,此時(shí)就需要重新走循環(huán)選擇初始化成功的計(jì)數(shù)器對(duì)象進(jìn)行操作 continue; } } //其它線程正在對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容或初始化指定索引位置上的計(jì)數(shù)器對(duì)象 collide = false; } else if (!wasUncontended) //wasUncontended為false則說明有其它線程正在對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作 //此時(shí)當(dāng)前線程則需要獲取其它索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作 wasUncontended = true; //嘗試對(duì)指定索引位置上的計(jì)數(shù)器對(duì)象進(jìn)行操作 else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) //執(zhí)行成功則退出 break; //校驗(yàn)當(dāng)前是否有線程對(duì)計(jì)數(shù)器數(shù)組進(jìn)行了擴(kuò)容,將計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象移動(dòng)到了新的計(jì)數(shù)器數(shù)組中 //如果已經(jīng)將計(jì)數(shù)器對(duì)象移動(dòng)到了新的計(jì)數(shù)器數(shù)組中,那重新選擇新的計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象來進(jìn)行操作 //如果沒有線程對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容,那比較一下當(dāng)前計(jì)數(shù)器數(shù)組的長度是否大于等于cpu的數(shù)量 //如果大于等于cpu的數(shù)量則不會(huì)進(jìn)行擴(kuò)容,只會(huì)重新選擇計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象來操作 //如果計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量那就會(huì)對(duì)計(jì)數(shù)器數(shù)組進(jìn)行2倍的擴(kuò)容 else if (counterCells != as || n >= NCPU) //此處將collide修改為false可能是想再次嘗試一下是否能對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作 //如果能操作則不進(jìn)行擴(kuò)容,這樣能減少擴(kuò)容帶來的性能問題 collide = false; else if (!collide) collide = true; //多次獲取計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象而不能操作,并計(jì)數(shù)器數(shù)組的長度小于cpu的數(shù)量 //導(dǎo)致部分的cpu沒有被使用到,此時(shí)就需要對(duì)計(jì)數(shù)器數(shù)組進(jìn)行擴(kuò)容,充分的使用cpu //對(duì)計(jì)數(shù)器數(shù)組加鎖 else if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { try { //校驗(yàn)計(jì)數(shù)器數(shù)組是否變更,防止其它線程已經(jīng)對(duì)計(jì)數(shù)器數(shù)組進(jìn)行了擴(kuò)容 if (counterCells == as) { //初始化一個(gè)新的計(jì)數(shù)器數(shù)組,大小是原來的2倍 CounterCell[] rs = new CounterCell[n << 1]; //循環(huán)將原來的計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象移動(dòng)到擴(kuò)容后的計(jì)數(shù)器數(shù)組中 for (int i = 0; i < n; ++i) rs[i] = as[i]; //更新當(dāng)前集合中的計(jì)數(shù)器數(shù)組 counterCells = rs; } } finally { //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí) cellsBusy = 0; } collide = false; //使用擴(kuò)容后的計(jì)數(shù)器數(shù)組進(jìn)行操作 continue; } //計(jì)數(shù)器對(duì)象正在被其它線程操作,此時(shí)需要重新生成隨機(jī)數(shù),獲取別的索引位置上的計(jì)數(shù)器對(duì)象來進(jìn)行操作 h = ThreadLocalRandom.advanceProbe(h); //計(jì)數(shù)器數(shù)組未被初始化,此時(shí)就需要校驗(yàn)一下當(dāng)前是否有其它線程正在初始化計(jì)數(shù)器數(shù)組 //如果沒有,那當(dāng)前線程就需要將計(jì)數(shù)器數(shù)組初始化的標(biāo)識(shí)設(shè)置為1,代表當(dāng)前有線程正在初始化計(jì)數(shù)器數(shù)組 } else if (cellsBusy == 0 && counterCells == as && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { boolean init = false; try { if (counterCells == as) { //創(chuàng)建計(jì)數(shù)器數(shù)組,計(jì)數(shù)器數(shù)組的長度為2的次方 CounterCell[] rs = new CounterCell[2]; //創(chuàng)建一個(gè)計(jì)數(shù)器對(duì)象,并將值添加到計(jì)數(shù)器對(duì)象中 //并使用線程生成的隨機(jī)數(shù)與計(jì)數(shù)器數(shù)組索引長度進(jìn)行運(yùn)算獲取到指定的索引位置 //并將計(jì)數(shù)器對(duì)象放置到指定的索引位置上 rs[h & 1] = new CounterCell(x); //更新當(dāng)前集合中的計(jì)數(shù)器數(shù)組 counterCells = rs; //初始化完成 init = true; } } finally { //修改計(jì)數(shù)器數(shù)組鎖標(biāo)識(shí) cellsBusy = 0; } if (init) //計(jì)數(shù)器數(shù)組初始化完成則退出 break; //計(jì)數(shù)器數(shù)組正在被其它線程初始化或已經(jīng)被其它線程初始化完成 //此時(shí)嘗試對(duì)基本的計(jì)數(shù)器值進(jìn)行操作 //如果失敗則說明有其它線程也在對(duì)基本計(jì)數(shù)器值進(jìn)行操作 //那就要重新走循環(huán),來看計(jì)數(shù)器數(shù)組是否被初始化完成,如果被初始化完成那就對(duì)計(jì)數(shù)器數(shù)組中的計(jì)數(shù)器對(duì)象進(jìn)行操作 //如果還沒有初始化完成則會(huì)繼續(xù)嘗試對(duì)基本的計(jì)數(shù)器值進(jìn)行操作 } else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) break; } }
我們?cè)倏?code>addCount中的第二個(gè)分支,該分支中主要是先校驗(yàn)是否到達(dá)了擴(kuò)容的閾值以及是否小于數(shù)組最大的容量大小,條件成立則會(huì)校驗(yàn)是否有線程在擴(kuò)容,如果有線程在擴(kuò)容,那當(dāng)前線程則需要協(xié)助擴(kuò)容如果沒有那當(dāng)前線程則開啟擴(kuò)容。
transfer
private final void transfer(Node<K, V>[] tab, Node<K, V>[] nextTab) { //n 數(shù)組長度 //stride 每個(gè)cpu需要負(fù)責(zé)轉(zhuǎn)移的索引長度 int n = tab.length, stride; //使用數(shù)組長度來計(jì)算每個(gè)cpu需要負(fù)責(zé)的長度 //每個(gè)cpu最少需要負(fù)責(zé)16的長度 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; //nextTab為空則說明當(dāng)前沒有其它線程在初始化 //不為空則說明當(dāng)前有其它線程正在初始化,當(dāng)前線程則需要幫助初始化的線程將舊數(shù)組中的元素節(jié)點(diǎn)轉(zhuǎn)移到新的數(shù)組中 if (nextTab == null) { try { //創(chuàng)建新的數(shù)組,容量是舊數(shù)組的2倍 @SuppressWarnings("unchecked") Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1]; nextTab = nt; } catch (Throwable ex) { //出現(xiàn)異常時(shí)則說明擴(kuò)容失敗,容量大小已經(jīng)超出MAXIMUM_CAPACITY參數(shù)指定的最大的數(shù)組容量 //因?yàn)镸AXIMUM_CAPACITY參數(shù)指定的容量大小是int類型中正整數(shù)最大的2的次方 //當(dāng)數(shù)組的長度已經(jīng)到達(dá)MAXIMUM_CAPACITY參數(shù)指定的容量大小時(shí),如果再進(jìn)行2倍的擴(kuò)容就會(huì)導(dǎo)致數(shù)組的長度變成負(fù)數(shù) //此時(shí)就會(huì)導(dǎo)致擴(kuò)容失敗,將int類型中最大的正整數(shù)設(shè)置成擴(kuò)容的閾值來停止本次擴(kuò)容 //如果數(shù)組的長度已到達(dá)數(shù)組最大的容量大小那就不會(huì)進(jìn)行擴(kuò)容了 sizeCtl = Integer.MAX_VALUE; return; } //擴(kuò)容后的數(shù)組,當(dāng)其它擴(kuò)容的線程發(fā)現(xiàn)nextTable不為空時(shí)則不會(huì)重復(fù)擴(kuò)容 nextTable = nextTab; //擴(kuò)容后,默認(rèn)需要轉(zhuǎn)移整個(gè)舊數(shù)組 transferIndex = n; } //獲取擴(kuò)容后的數(shù)組長度 int nextn = nextTab.length; //創(chuàng)建一個(gè)轉(zhuǎn)移的節(jié)點(diǎn) ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab); //是否繼續(xù)轉(zhuǎn)移 boolean advance = true; //標(biāo)識(shí)擴(kuò)容之后舊數(shù)組中的節(jié)點(diǎn)是否全部完成轉(zhuǎn)移 boolean finishing = false; for (int i = 0, bound = 0; ; ) { Node<K, V> f; int fh; while (advance) { //nextIndex 當(dāng)前線程開始轉(zhuǎn)移的長度位置 //nextBound 當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的索引位置邊界,如果到達(dá)這個(gè)邊界則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點(diǎn) //轉(zhuǎn)移舊數(shù)組中的元素節(jié)點(diǎn)是從數(shù)組的尾部向數(shù)組的頭部開始轉(zhuǎn)移 int nextIndex, nextBound; //每次轉(zhuǎn)移完一個(gè)索引位置上的節(jié)點(diǎn)的時(shí)候都會(huì)校驗(yàn)一下下一次轉(zhuǎn)移的索引位置是否已經(jīng)超出邊界 //或舊數(shù)組中的元素節(jié)點(diǎn)都已經(jīng)全部轉(zhuǎn)移完成 //如果下一次轉(zhuǎn)移的索引位置超出邊界,但是剩下需要轉(zhuǎn)移的節(jié)點(diǎn)沒有被其它線程協(xié)助轉(zhuǎn)移 //那么當(dāng)前線程則繼續(xù)選擇部分的索引長度來轉(zhuǎn)移 if (--i >= bound || finishing) advance = false; //校驗(yàn)剩余需要轉(zhuǎn)移的索引長度是否為0,如果為0則說明舊數(shù)組中沒有需要轉(zhuǎn)移的元素節(jié)點(diǎn)了 else if ((nextIndex = transferIndex) <= 0) { //將轉(zhuǎn)移的節(jié)點(diǎn)的索引位置設(shè)置為-1,后續(xù)會(huì)根據(jù)該條件退出擴(kuò)容方法 i = -1; advance = false; //根據(jù)開始轉(zhuǎn)移的長度位置與每個(gè)線程需要轉(zhuǎn)移的長度進(jìn)行比較 //如果開始轉(zhuǎn)移的長度位置大于每個(gè)線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個(gè)線程需要轉(zhuǎn)移的長度 //獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置邊界 //如果開始轉(zhuǎn)移的長度位置小于等于每個(gè)線程需要轉(zhuǎn)移的長度,那就說明當(dāng)前需要轉(zhuǎn)移的長度不需要其它線程協(xié)助 //當(dāng)前線程則會(huì)將剩下需要轉(zhuǎn)移的節(jié)點(diǎn)轉(zhuǎn)移到新的數(shù)組中 } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { //將計(jì)算后的索引邊界賦予bound,用于線程每次轉(zhuǎn)移之后進(jìn)行校驗(yàn) //如果已經(jīng)到達(dá)了邊界說明線程已經(jīng)完成了所負(fù)責(zé)的索引長度的節(jié)點(diǎn)轉(zhuǎn)移 //線程不再執(zhí)行節(jié)點(diǎn)的轉(zhuǎn)移 bound = nextBound; //開始轉(zhuǎn)移的長度位置-1,獲取到開始轉(zhuǎn)移的節(jié)點(diǎn)的索引位置 i = nextIndex - 1; advance = false; } } //i < 0 小于0則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的節(jié)點(diǎn)已經(jīng)完成并且沒有其它需要轉(zhuǎn)移的節(jié)點(diǎn)了 //i >= n //i + n >= nextn if (i < 0 || i >= n || i + n >= nextn) { int sc; //當(dāng)前finishing為true時(shí)則說明舊數(shù)組中的所有節(jié)點(diǎn)都已經(jīng)轉(zhuǎn)移 //此時(shí)就需要將新數(shù)組設(shè)置為當(dāng)前集合使用的數(shù)組并計(jì)算下一次的擴(kuò)容閾值 if (finishing) { //將擴(kuò)容的數(shù)組置為空,代表當(dāng)前沒有線程正在進(jìn)行擴(kuò)容操作 nextTable = null; //將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組 table = nextTab; //計(jì)算下一次擴(kuò)容的閾值 sizeCtl = (n << 1) - (n >>> 1); //擴(kuò)容完成,退出 return; } //finishing為false則會(huì)走到當(dāng)前語句,則說明當(dāng)前線程并不知道舊數(shù)組中的元素節(jié)點(diǎn)有沒有轉(zhuǎn)移完成 //但是當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的索引節(jié)點(diǎn)已經(jīng)完成,此時(shí)就需要將并發(fā)擴(kuò)容中的線程數(shù)量減1 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { //擴(kuò)容標(biāo)記左移16位獲取到線程數(shù)量的標(biāo)記 //使用sc-2獲取到當(dāng)前還剩下的線程數(shù)量的標(biāo)記 //如果兩個(gè)相等則說明當(dāng)前線程是最后一個(gè)擴(kuò)容結(jié)束的線程 //此時(shí)就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點(diǎn)遍歷來檢查是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了 //如果都轉(zhuǎn)移了,則將擴(kuò)容后的新數(shù)組設(shè)置為當(dāng)前集合正在使用的數(shù)組,并且計(jì)算下一次擴(kuò)容的閾值 //如果還有元素節(jié)點(diǎn)沒有轉(zhuǎn)移,當(dāng)前線程則會(huì)將剩下的元素節(jié)點(diǎn)進(jìn)行轉(zhuǎn)移 //如果兩個(gè)不相等則說明當(dāng)前線程不是最后一個(gè)擴(kuò)容結(jié)束的線程 //當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點(diǎn),并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點(diǎn)了,當(dāng)前線程直接退出擴(kuò)容 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; //當(dāng)前線程是最后一個(gè)結(jié)束擴(kuò)容的線程,此時(shí)就需要檢查舊數(shù)組中是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了 finishing = advance = true; //從舊數(shù)組的尾部開始檢查 i = n; } } else if ((f = tabAt(tab, i)) == null) //如果指定索引位置上的節(jié)點(diǎn)為空則直接將舊數(shù)組中該索引位置上的節(jié)點(diǎn)設(shè)置成一個(gè)正在轉(zhuǎn)移的節(jié)點(diǎn)進(jìn)行占位 //當(dāng)有新的線程要對(duì)該索引位置的節(jié)點(diǎn)操作的時(shí)候則會(huì)發(fā)現(xiàn)該索引位置上的節(jié)點(diǎn)是一個(gè)正在轉(zhuǎn)移的節(jié)點(diǎn) //則不會(huì)對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作而是先協(xié)助擴(kuò)容線程進(jìn)行轉(zhuǎn)移 advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) //指定索引位置上的節(jié)點(diǎn)已經(jīng)被其它線程處理過 advance = true; else { //加鎖,防止其它線程對(duì)當(dāng)前需要移動(dòng)的節(jié)點(diǎn)進(jìn)行操作 synchronized (f) { //校驗(yàn)節(jié)點(diǎn)是否變更 if (tabAt(tab, i) == f) { //ln 低位節(jié)點(diǎn),該節(jié)點(diǎn)放置到新數(shù)組中的索引位置與在舊數(shù)組中的索引位置相同 //hn 高位節(jié)點(diǎn),該節(jié)點(diǎn)放置到新數(shù)組中的索引位置是在舊數(shù)組中的索引位置加上舊數(shù)組的長度 Node<K, V> ln, hn; //校驗(yàn)節(jié)點(diǎn)的hash值是否大于等于0 //如果節(jié)點(diǎn)的hash值大于等于0則說明該索引位置上只有一個(gè)節(jié)點(diǎn)或節(jié)點(diǎn)是一個(gè)鏈表節(jié)點(diǎn) if (fh >= 0) { //使用節(jié)點(diǎn)的hash值與舊數(shù)組的長度進(jìn)行與運(yùn)算 //與運(yùn)算后的結(jié)果分為0和1,0則將該節(jié)點(diǎn)放置到低位,1則將節(jié)點(diǎn)放置高位 int runBit = fh & n; //避免后續(xù)轉(zhuǎn)移節(jié)點(diǎn)的時(shí)候沒有必要的循環(huán)以及創(chuàng)建節(jié)點(diǎn) Node<K, V> lastRun = f; //遍歷整個(gè)鏈表來決定從那個(gè)節(jié)點(diǎn)開始以及后續(xù)的節(jié)點(diǎn)都是沒有必要遍歷和創(chuàng)建的 //而是直接使用指針指向舊數(shù)組中的節(jié)點(diǎn),當(dāng)擴(kuò)容完成之后舊數(shù)組以及舊數(shù)組中部分沒有被指針引用的節(jié)點(diǎn)則會(huì)被回收 for (Node<K, V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } //沒有必要循環(huán)和創(chuàng)建的節(jié)點(diǎn)應(yīng)該放置到新數(shù)組中的低為還是高位 if (runBit == 0) { //低位 ln = lastRun; hn = null; } else { //高位 hn = lastRun; ln = null; } //從頭節(jié)點(diǎn)開始遍歷將需要重新創(chuàng)建的節(jié)點(diǎn)進(jìn)行創(chuàng)建并添加到高位或低位 //在添加到高位或低位的時(shí)候,新創(chuàng)建的節(jié)點(diǎn)使用的是頭插法 //如果有節(jié)點(diǎn)不需要重新創(chuàng)建,那不需要重新創(chuàng)建的節(jié)點(diǎn)則會(huì)放到高位或低位節(jié)點(diǎn)的尾部 //在上面的時(shí)候如果不需要重新創(chuàng)建的節(jié)點(diǎn)其實(shí)就已經(jīng)放在了ln或hn中 //當(dāng)有節(jié)點(diǎn)重新創(chuàng)建了的時(shí)候,則ln或hn設(shè)置為新創(chuàng)建的節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn) //其實(shí)整體來說就是頭插法,如果說整個(gè)鏈表或部分連續(xù)的鏈表節(jié)點(diǎn)不需要重新創(chuàng)建的時(shí)候還是保持在舊數(shù)組中一樣的順序 for (Node<K, V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) //低位 ln = new Node<K, V>(ph, pk, pv, ln); else //高位 hn = new Node<K, V>(ph, pk, pv, hn); } //將低位節(jié)點(diǎn)鏈表添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同 setTabAt(nextTab, i, ln); //將高位節(jié)點(diǎn)鏈表添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度 setTabAt(nextTab, i + n, hn); //將舊數(shù)組中的索引位置上的節(jié)點(diǎn)設(shè)置為一個(gè)已經(jīng)轉(zhuǎn)移的節(jié)點(diǎn) setTabAt(tab, i, fwd); //繼續(xù)推進(jìn)下一次節(jié)點(diǎn)轉(zhuǎn)移 advance = true; //節(jié)點(diǎn)是一個(gè)紅黑樹節(jié)點(diǎn) } else if (f instanceof TreeBin) { TreeBin<K, V> t = (TreeBin<K, V>) f; //低位頭節(jié)點(diǎn)和尾節(jié)點(diǎn) TreeNode<K, V> lo = null, loTail = null; //高位頭節(jié)點(diǎn)和尾節(jié)點(diǎn) TreeNode<K, V> hi = null, hiTail = null; //低位節(jié)點(diǎn)和高位節(jié)點(diǎn)的數(shù)量 int lc = 0, hc = 0; //從TreeBin對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷將每一個(gè)節(jié)點(diǎn)分為低位節(jié)點(diǎn)和高位節(jié)點(diǎn) for (Node<K, V> e = t.first; e != null; e = e.next) { int h = e.hash; //構(gòu)造新的樹節(jié)點(diǎn) TreeNode<K, V> p = new TreeNode<K, V>(h, e.key, e.val, null, null); if ((h & n) == 0) { //將構(gòu)造的樹節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)指針指向原低位尾節(jié)點(diǎn) //如果原低尾節(jié)點(diǎn)為空則說明當(dāng)前樹節(jié)點(diǎn)是第一個(gè)節(jié)點(diǎn) //那就將當(dāng)前樹節(jié)點(diǎn)設(shè)置為低位頭節(jié)點(diǎn) if ((p.prev = loTail) == null) lo = p; else //原低位尾節(jié)點(diǎn)不為空則將原尾節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)指針指向當(dāng)前樹節(jié)點(diǎn) loTail.next = p; //將新構(gòu)造的樹節(jié)點(diǎn)設(shè)置為低位尾節(jié)點(diǎn) loTail = p; //低位節(jié)點(diǎn)的數(shù)量自增 ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } //拆分后的高低位節(jié)點(diǎn)的數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值 //如果小于等于則調(diào)用untreeify方法將樹節(jié)點(diǎn)轉(zhuǎn)換為鏈表節(jié)點(diǎn) //如果高位節(jié)點(diǎn)數(shù)量或低位節(jié)點(diǎn)數(shù)量大于閾值則會(huì)校驗(yàn)對(duì)方節(jié)點(diǎn)的數(shù)量是否不等于0 //如果說對(duì)方的節(jié)點(diǎn)數(shù)量等于0則說明節(jié)點(diǎn)并沒有拆分為高位或低位節(jié)點(diǎn) //那就使用原本的TreeBin對(duì)象進(jìn)行轉(zhuǎn)移 //如果對(duì)方的節(jié)點(diǎn)數(shù)量大于0則說明已經(jīng)拆分為了高低位節(jié)點(diǎn) //此時(shí)就需要將高低位節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹并封裝成一個(gè)TreeBin對(duì)象 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K, V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K, V>(hi) : t; //將低位節(jié)點(diǎn)TreeBin對(duì)象添加到新數(shù)組中所在的索引位置與舊數(shù)組所在的索引位置相同 setTabAt(nextTab, i, ln); //將高位節(jié)點(diǎn)TreeBin添加到新數(shù)組中所在的索引位置是在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度 setTabAt(nextTab, i + n, hn); //將舊數(shù)組中的索引位置上的節(jié)點(diǎn)設(shè)置為一個(gè)已經(jīng)轉(zhuǎn)移的節(jié)點(diǎn) setTabAt(tab, i, fwd); //繼續(xù)推進(jìn)下一次節(jié)點(diǎn)轉(zhuǎn)移 advance = true; } } } } } }
transfer
方法中的代碼比較多,我們就一段一段的講解。
int n = tab.length, stride; if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; if (nextTab == null) { try { @SuppressWarnings("unchecked") Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1]; nextTab = nt; } catch (Throwable ex) { sizeCtl = Integer.MAX_VALUE; return; } nextTable = nextTab; transferIndex = n; }
首先會(huì)通過舊數(shù)組的長度來計(jì)算每個(gè)cpu
在轉(zhuǎn)移舊數(shù)組中的節(jié)點(diǎn)所需要負(fù)責(zé)的區(qū)域長度,每個(gè)cpu
最少需要負(fù)責(zé)16個(gè)區(qū)域的長度。 nextTab == null
則是校驗(yàn)是否有線程已經(jīng)在對(duì)新的數(shù)組進(jìn)行初始化了,如果沒有那當(dāng)前線程則會(huì)去初始化新的數(shù)組,新數(shù)組的長度則是舊數(shù)組的2倍,如果出現(xiàn)異常則說明舊數(shù)組的長度已經(jīng)到達(dá)了數(shù)組最大的容量大小了,此時(shí)就不能再繼續(xù)擴(kuò)容了,數(shù)組最大的容量大小為2的30次方
,則是int
類型中正整數(shù)最大的2的次方,如果再次進(jìn)行擴(kuò)容的話就會(huì)導(dǎo)致數(shù)組的容量大小變成負(fù)數(shù)。
ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab); ForwardingNode(Node<K,V>[] tab) { super(MOVED, null, null, null); this.nextTable = tab; }
ForwardingNode
節(jié)點(diǎn)是一個(gè)占位節(jié)點(diǎn),當(dāng)將舊數(shù)組中指定索引位置上的所有節(jié)點(diǎn)都轉(zhuǎn)移到了新的數(shù)組中,則會(huì)使用該節(jié)點(diǎn)進(jìn)行占位,告知其它線程該索引位置上的節(jié)點(diǎn)都已經(jīng)被轉(zhuǎn)移到了新的數(shù)組中。
while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } }
while
循環(huán)的作用主要是為每個(gè)線程分配所負(fù)責(zé)的區(qū)域以及推進(jìn)轉(zhuǎn)移的進(jìn)度。
--i >= bound || finishing
:--i >= bound
則是每次轉(zhuǎn)移完一個(gè)索引位置上的節(jié)點(diǎn)的時(shí)候都會(huì)校驗(yàn)一下下一次轉(zhuǎn)移的索引位置的節(jié)點(diǎn)是否已經(jīng)超出當(dāng)前線程負(fù)責(zé)轉(zhuǎn)移的邊界了,而finishing
則是校驗(yàn)舊數(shù)組中所有的元素節(jié)點(diǎn)是否都已經(jīng)完成了轉(zhuǎn)移。
(nextIndex = transferIndex) <= 0
:剩余需要轉(zhuǎn)移的區(qū)域的長度是否小于等于0,小于等于0則說明舊數(shù)組中的元素節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移完成了,在開始準(zhǔn)備轉(zhuǎn)移的時(shí)候該值為舊數(shù)組的長度,每當(dāng)有一個(gè)線程來協(xié)助擴(kuò)容的時(shí)候則會(huì)從該值中取一部分長度來負(fù)責(zé)轉(zhuǎn)移。
條件三則是根據(jù)開始轉(zhuǎn)移的長度位置與每個(gè)線程需要轉(zhuǎn)移的長度進(jìn)行比較,如果開始轉(zhuǎn)移的長度位置大于每個(gè)線程需要轉(zhuǎn)移的長度,那就使用開始轉(zhuǎn)移的長度位置減去每個(gè)線程需要轉(zhuǎn)移的長度,獲取到當(dāng)前線程需要負(fù)責(zé)轉(zhuǎn)移的索引位置的邊界,再將剩余需要轉(zhuǎn)移的長度存放到transferIndex
中,等待其它線程協(xié)助或者說等待當(dāng)前線程轉(zhuǎn)移完所負(fù)責(zé)的區(qū)域之后繼續(xù)轉(zhuǎn)移剩余的長度,如果開始轉(zhuǎn)移的長度位置小于等于每個(gè)線程所需要轉(zhuǎn)移的長度,那就說明當(dāng)前線程自己可以完成轉(zhuǎn)移,不需要其它線程協(xié)助,轉(zhuǎn)移節(jié)點(diǎn)的時(shí)候則是從數(shù)組的尾部向前推進(jìn)。
if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; i = n; } }
i小于0
則說明當(dāng)前線程所負(fù)責(zé)轉(zhuǎn)移的區(qū)域節(jié)點(diǎn)已經(jīng)轉(zhuǎn)移完成,并且數(shù)組中沒有其它需要轉(zhuǎn)移的節(jié)點(diǎn)了,此時(shí)就需要通過下面的cas
操作將sizeCtl
中的擴(kuò)容線程數(shù)量減1,線程數(shù)量減1之后會(huì)去校驗(yàn)當(dāng)前線程是否是最后一個(gè)擴(kuò)容的線程,如果不是則說明當(dāng)前線程已經(jīng)完成了所負(fù)責(zé)的索引位置的元素節(jié)點(diǎn)轉(zhuǎn)移,并且舊數(shù)組中沒有其他需要轉(zhuǎn)移的節(jié)點(diǎn)了,當(dāng)前線程直接退出擴(kuò)容,如果是最后一個(gè)擴(kuò)容的線程,此時(shí)就需要當(dāng)前線程執(zhí)行收尾操作,需要從舊數(shù)組中的尾部開始向頭部節(jié)點(diǎn)變量來檢查是否所有的元素節(jié)點(diǎn)都轉(zhuǎn)移了,如果還有沒被轉(zhuǎn)移的元素節(jié)點(diǎn),那當(dāng)前線程則會(huì)去進(jìn)行轉(zhuǎn)移,當(dāng)檢查完成之后會(huì)將新的數(shù)組替換舊的數(shù)組,并計(jì)算下一次擴(kuò)容的閾值。
else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd);
如果指定索引位置上的節(jié)點(diǎn)為空則通過cas操作將舊數(shù)組中該索引位置上的節(jié)點(diǎn)設(shè)置成ForwardingNode
節(jié)點(diǎn)進(jìn)行占位,告知其它線程該索引位置上的節(jié)點(diǎn)已經(jīng)進(jìn)行了轉(zhuǎn)移,其它線程則不會(huì)對(duì)該索引位置上的節(jié)點(diǎn)進(jìn)行操作,而是先協(xié)助擴(kuò)容線程進(jìn)行節(jié)點(diǎn)轉(zhuǎn)移。
else if ((fh = f.hash) == MOVED) advance = true;
索引位置上的節(jié)點(diǎn)的hash
值為MOVED
,則說明該索引位置上的節(jié)點(diǎn)都已經(jīng)轉(zhuǎn)移了,當(dāng)前線程則繼續(xù)推進(jìn)索引位置轉(zhuǎn)節(jié)點(diǎn)。
synchronized (f) { if (tabAt(tab, i) == f) { Node<K, V> ln, hn; if (fh >= 0) { int runBit = fh & n; Node<K, V> lastRun = f; for (Node<K, V> p = f.next; p != null; p = p.next) { int b = p.hash & n; if (b != runBit) { runBit = b; lastRun = p; } } if (runBit == 0) { ln = lastRun; hn = null; } else { hn = lastRun; ln = null; } for (Node<K, V> p = f; p != lastRun; p = p.next) { int ph = p.hash; K pk = p.key; V pv = p.val; if ((ph & n) == 0) ln = new Node<K, V>(ph, pk, pv, ln); else hn = new Node<K, V>(ph, pk, pv, hn); } setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } else if (f instanceof TreeBin) { TreeBin<K, V> t = (TreeBin<K, V>) f; TreeNode<K, V> lo = null, loTail = null; TreeNode<K, V> hi = null, hiTail = null; int lc = 0, hc = 0; for (Node<K, V> e = t.first; e != null; e = e.next) { int h = e.hash; TreeNode<K, V> p = new TreeNode<K, V>(h, e.key, e.val, null, null); if ((h & n) == 0) { if ((p.prev = loTail) == null) lo = p; else loTail.next = p; loTail = p; ++lc; } else { if ((p.prev = hiTail) == null) hi = p; else hiTail.next = p; hiTail = p; ++hc; } } ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : (hc != 0) ? new TreeBin<K, V>(lo) : t; hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : (lc != 0) ? new TreeBin<K, V>(hi) : t; setTabAt(nextTab, i, ln); setTabAt(nextTab, i + n, hn); setTabAt(tab, i, fwd); advance = true; } } }
我們?cè)倏凑嬲D(zhuǎn)移節(jié)點(diǎn)的代碼,分為轉(zhuǎn)移鏈表節(jié)點(diǎn)和轉(zhuǎn)移紅黑樹節(jié)點(diǎn)。
轉(zhuǎn)移鏈表節(jié)點(diǎn):鏈表節(jié)點(diǎn)會(huì)被分為高位節(jié)點(diǎn)鏈表(hn)
和低位節(jié)點(diǎn)鏈表(ln)
,首先通過頭節(jié)點(diǎn)的hash
值與舊數(shù)組的長度進(jìn)行與運(yùn)算,與運(yùn)算后的結(jié)果分為0和1,0則將節(jié)點(diǎn)放置到低位,1將該節(jié)點(diǎn)放置到高位,然后遍歷整個(gè)鏈表來決定從那個(gè)節(jié)點(diǎn)開始以及后續(xù)的節(jié)點(diǎn)都是沒有必要重新創(chuàng)建的,而是直接使用指針指向舊數(shù)組中的節(jié)點(diǎn),lastRun
指針指向的節(jié)點(diǎn)以及后續(xù)的節(jié)點(diǎn)都是沒有必要?jiǎng)?chuàng)建的,因?yàn)槟愫罄m(xù)的鏈表節(jié)點(diǎn)沒有被拆分為高位或低位,是一個(gè)連續(xù)存放在高位或低位的鏈表節(jié)點(diǎn),所以說不需要重新創(chuàng)建節(jié)點(diǎn),比如說一個(gè)鏈表中的所有節(jié)點(diǎn)都是放在低位節(jié)點(diǎn)中的,那lastRun
指針指向的就是鏈表的頭節(jié)點(diǎn),該鏈表中的節(jié)點(diǎn)并沒有改動(dòng),并不需要重新創(chuàng)建節(jié)點(diǎn),而是直接將鏈表中的頭節(jié)點(diǎn)放置到新的數(shù)組中即可,當(dāng)區(qū)分了哪些節(jié)點(diǎn)是不需要重新創(chuàng)建的,則會(huì)將不需要重新創(chuàng)建的節(jié)點(diǎn)的頭節(jié)點(diǎn)lastRun
賦值給高位或低位,然后從鏈表的頭節(jié)點(diǎn)開始遍歷將需要?jiǎng)?chuàng)建的節(jié)點(diǎn)進(jìn)行創(chuàng)建并添加到高位鏈表或低位鏈表中,在添加到高位鏈表或低位鏈表的時(shí)候,節(jié)點(diǎn)使用的是頭插法,創(chuàng)建完成之后,低位節(jié)點(diǎn)鏈表會(huì)放置到新數(shù)組中所在的索引位置與節(jié)點(diǎn)在舊數(shù)組中所在的索引位置相同,高位節(jié)點(diǎn)鏈表會(huì)放置到新數(shù)組中所在的索引位置是節(jié)點(diǎn)在舊數(shù)組中所在的索引位置加上舊數(shù)組的長度,當(dāng)高低位節(jié)點(diǎn)鏈表都轉(zhuǎn)移完成之后則會(huì)在舊數(shù)組中該索引位置上添加到一個(gè)占位節(jié)點(diǎn)。
下面的圖中ln
則是低位節(jié)點(diǎn)鏈表,hn
則是高位節(jié)點(diǎn)鏈表,在某些情況下有些節(jié)點(diǎn)并不需要重新創(chuàng)建,而是使用原來的節(jié)點(diǎn),最差的情況下就是只有鏈表節(jié)點(diǎn)的尾部的一個(gè)節(jié)點(diǎn)不需要重新創(chuàng)建。
紅黑樹節(jié)點(diǎn):從TreeBin
對(duì)象中記錄的鏈表的頭節(jié)點(diǎn)開始遍歷,將每一個(gè)節(jié)點(diǎn)轉(zhuǎn)換為新的樹節(jié)點(diǎn),并分為高低位鏈表節(jié)點(diǎn),校驗(yàn)高低位鏈表節(jié)點(diǎn)中的節(jié)點(diǎn)數(shù)量是否小于等于紅黑樹轉(zhuǎn)鏈表的閾值,如果小于等于則會(huì)將高低位鏈表節(jié)點(diǎn)中的所有樹節(jié)點(diǎn)都轉(zhuǎn)換為普通的Node
節(jié)點(diǎn),如果不小于等于則會(huì)將高低位鏈表節(jié)點(diǎn)轉(zhuǎn)換為紅黑樹。 如果說高位或低位是一個(gè)鏈表節(jié)點(diǎn)的話,則會(huì)將鏈表的頭節(jié)點(diǎn)放置到新的數(shù)組中,如果是紅黑樹的話,則會(huì)將TreeBin
對(duì)象放置到新的數(shù)組中,然后再將舊數(shù)組中的索引位置上添加一個(gè)占位節(jié)點(diǎn)。
注意:其實(shí)在轉(zhuǎn)移舊數(shù)組中的節(jié)點(diǎn)的時(shí)候是有問題的,有可能會(huì)造成節(jié)點(diǎn)數(shù)據(jù)的丟失
線程A獲取到了索引位置上的鏈表節(jié)點(diǎn),頭節(jié)點(diǎn)類型為Node
,準(zhǔn)備對(duì)該鏈表節(jié)點(diǎn)加synchronized
鎖進(jìn)行轉(zhuǎn)移時(shí),此時(shí)線程B先加了synchronized
鎖,對(duì)該索引位置上的鏈表節(jié)點(diǎn)添加了新的節(jié)點(diǎn)并將鏈表節(jié)點(diǎn)轉(zhuǎn)換為了紅黑樹,并將TreeBin
對(duì)象放置在了該索引位置上,當(dāng)線程B釋放了鎖之后,線程A獲取到了鎖后去判斷之前獲取到的頭節(jié)點(diǎn)Node
是否與索引位置上最新的TreeBin
對(duì)象相同,顯然是不相同的,當(dāng)不相同的情況下就會(huì)跳過該索引位置上的節(jié)點(diǎn)的轉(zhuǎn)移,在上面說過,當(dāng)擴(kuò)容完成時(shí),最后一個(gè)線程會(huì)去檢查舊數(shù)組中是否還有節(jié)點(diǎn)沒有轉(zhuǎn)移,如果有則會(huì)進(jìn)行轉(zhuǎn)移,如果說在檢查轉(zhuǎn)移的時(shí)候也遇到了上面類似的問題,刪除節(jié)點(diǎn)的時(shí)候?qū)?code>TreeBin轉(zhuǎn)換為Node
,是不是就會(huì)跳過該索引位置上的檢查,從而導(dǎo)致節(jié)點(diǎn)數(shù)據(jù)丟失。
以上就是Java并發(fā)源碼分析ConcurrentHashMap線程集合的詳細(xì)內(nèi)容,更多關(guān)于Java ConcurrentHashMap的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
gradle使用maven-publish發(fā)布jar包上傳到私有maven配置
這篇文章主要介紹了gradle使用maven-publish發(fā)布jar包上傳到私有maven的配置示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例
這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-10-10Java中Date與String相互轉(zhuǎn)換的方法
這篇文章主要為大家詳細(xì)介紹了Java中Date與String相互轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10Maven項(xiàng)目部署到Jboss出現(xiàn)Failed to create a new SAX parser
這篇文章主要為大家詳細(xì)介紹了Maven項(xiàng)目部署到Jboss出現(xiàn)Failed to create a new SAX parser的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11新版IDEA使用Spring Initializr創(chuàng)建工程的兩種方法
這篇文章主要介紹了新版IDEA使用Spring Initializr創(chuàng)建工程(兩種方法,官方工具和IDEA),文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下2024-10-10