欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)源碼分析ConcurrentHashMap線程集合

 更新時(shí)間:2023年02月01日 15:16:49   作者:歷河川  
這篇文章主要為大家介紹了Java并發(fā)源碼分析ConcurrentHashMap線程集合,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

簡(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)用的其它一些方法比較難以理解,我們就一步步的來看。

首先如果keyvaluenull的情況下是不能添加到當(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ù)指定的keyvalue創(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ò)容的方法,局部變量nextTabsc則是最新的擴(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ì)為指定的keyvalue創(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方法將指定的keyvalue添加到紅黑樹中,如果說指定的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;
}

putTreeValbalanceInsertion方法在之前的HashMap中已經(jīng)講過,此處就不再講解,我們主要還是看一下lockRootunlockRoot方法。

鎖狀態(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配置

    這篇文章主要介紹了gradle使用maven-publish發(fā)布jar包上傳到私有maven的配置示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • maven?repository詳解

    maven?repository詳解

    這篇文章主要介紹了maven?repository的相關(guān)知識(shí),本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • Java中的排序Comparator類用法詳解

    Java中的排序Comparator類用法詳解

    這篇文章主要介紹了Java中的排序Comparator類用法詳解,Comparator?類常作為?sorted()?方法的參數(shù)傳遞給?sorted?方法,用來解決給集合排序,自定義排序規(guī)則的問題,需要的朋友可以參考下
    2023-08-08
  • java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例

    java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例

    這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)之二分查找法 binarySearch的實(shí)例的相關(guān)資料,希望通過本文能幫助到大家,讓大家理解掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-10-10
  • Java面向?qū)ο笾畠?nèi)部類詳解

    Java面向?qū)ο笾畠?nèi)部類詳解

    在 Java 中,允許一個(gè)類的定義位于另一個(gè)類的內(nèi)部,前者稱為內(nèi)部類,后者稱為外部類。這篇文章將總結(jié)一下內(nèi)部類的使用,感興趣的可以了解一下
    2022-10-10
  • 如何獲取?Spring?heapdump中的明文密碼

    如何獲取?Spring?heapdump中的明文密碼

    Actuator是Spring?Boot提供的應(yīng)用系統(tǒng)監(jiān)控的開源框架,在攻防場(chǎng)景里經(jīng)常會(huì)遇到Actuator配置不當(dāng)?shù)那闆r,攻擊者可以直接下載heapdump堆轉(zhuǎn)儲(chǔ)文件,本文介紹如何獲取?Spring?heapdump中的密碼明文,需要的朋友可以參考下
    2022-07-07
  • 詳解JAVA常用的時(shí)間操作【實(shí)用】

    詳解JAVA常用的時(shí)間操作【實(shí)用】

    本文主要介紹了JAVA一些常用的時(shí)間操作,很實(shí)用,相信大家在開發(fā)項(xiàng)目時(shí)會(huì)用到,下面就跟小編一起來看下吧
    2016-12-12
  • Java中Date與String相互轉(zhuǎn)換的方法

    Java中Date與String相互轉(zhuǎn)換的方法

    這篇文章主要為大家詳細(xì)介紹了Java中Date與String相互轉(zhuǎn)換方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Maven項(xiàng)目部署到Jboss出現(xiàn)Failed to create a new SAX parser

    Maven項(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使用Spring Initializr創(chuàng)建工程(兩種方法,官方工具和IDEA),文中通過代碼示例和圖文結(jié)合的方式給大家講解的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2024-10-10

最新評(píng)論