最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺析Java中ConcurrentHashMap的存儲(chǔ)流程

 更新時(shí)間:2023年05月23日 10:25:15   作者:愛敲代碼的小黃  
ConcurrentHashMap技術(shù)在互聯(lián)網(wǎng)技術(shù)使用如此廣泛,幾乎所有的后端技術(shù)面試官都要在ConcurrentHashMap技術(shù)的使用和原理方面對(duì)小伙伴們進(jìn)行360°的刁難,本文詳細(xì)給大家介紹一下ConcurrentHashMap的存儲(chǔ)流程,需要的朋友可以參考下

一、引言

ConcurrentHashMap技術(shù)在互聯(lián)網(wǎng)技術(shù)使用如此廣泛,幾乎所有的后端技術(shù)面試官都要在ConcurrentHashMap技術(shù)的使用和原理方面對(duì)小伙伴們進(jìn)行 360° 的刁難。

作為一個(gè)在互聯(lián)網(wǎng)公司面一次拿一次 Offer 的面霸,打敗了無數(shù)競(jìng)爭(zhēng)對(duì)手,每次都只能看到無數(shù)落寞的身影失望的離開,略感愧疚(請(qǐng)?jiān)试S我使用一下夸張的修辭手法)。

于是在一個(gè)寂寞難耐的夜晚,暖男我痛定思痛,決定開始寫 《吊打面試官》 系列,希望能幫助各位讀者以后面試勢(shì)如破竹,對(duì)面試官進(jìn)行 360° 的反擊,吊打問你的面試官,讓一同面試的同僚瞠目結(jié)舌,瘋狂收割大廠 Offer!

雖然現(xiàn)在是互聯(lián)網(wǎng)寒冬,但乾坤未定,你我皆是黑馬

二、使用

我們經(jīng)常在線程安全的場(chǎng)景下使用 ConcurrentHashMap,基本使用如下:

public class ConcurrentHashMapTest {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        map.put("test1", "1");
        map.put("test2", "2");
        map.put("test3", "3");
        map.remove("test1");
        System.out.println(map.get("test1"));
        System.out.println(map.get("test2"));
    }
}

使用的話,我相信大部分的讀者應(yīng)該都會(huì)的,這里小黃也不多介紹了,我們直接進(jìn)入正題

三、源碼

1、初始化

還是我們的老樣子,從初始化開始聊源碼

如果我們初始化時(shí)不攜帶入?yún)⒌脑挘跏蓟椒ㄈ缦拢?strong>可以看到,基本沒有什么東西

public ConcurrentHashMap() {}

如果你攜帶了入?yún)⒌脑?,初始化方法如下?/p>

public ConcurrentHashMap(int initialCapacity) {
    // 假如哥們傳進(jìn)來入?yún)⑿∮?
    if (initialCapacity < 0)
        // 直接拋出異常,說明哥們?cè)诟阈?
        throw new IllegalArgumentException();
    // 用傳進(jìn)來的數(shù)值與 MAXIMUM_CAPACITY >>> 1 進(jìn)行對(duì)比
    // 若大于則使用MAXIMUM_CAPACITY
    // 小于則使用距離initialCapacity最近的2次冪
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
               MAXIMUM_CAPACITY :
               tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
    this.sizeCtl = cap;
}

// 根據(jù)傳進(jìn)的C的數(shù)值,找到其距離最近的2次冪
private static final int tableSizeFor(int c) {
    int n = c - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

2、存儲(chǔ)操作

public V put(K key, V value) {
    // 在調(diào)用put方法時(shí),會(huì)調(diào)用putVal,第三個(gè)參數(shù)默認(rèn)傳遞為false
    // 在調(diào)用putIfAbsent時(shí),會(huì)調(diào)用putVal方法,第三個(gè)參數(shù)傳遞的為true
    // 如果傳遞為false,代表key一致時(shí),直接覆蓋數(shù)據(jù)
    // 如果傳遞為true,代表key一致時(shí),什么都不做,key不存在,正常添加(Redis,setnx)
    return putVal(key, value, false);
}

2.1 計(jì)算索引下標(biāo)

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 如果當(dāng)前的key或者value是空的話,直接拋出異常
    if (key == null || value == null){
        throw new NullPointerException();
    }
    // 獲取其下標(biāo)
    int hash = spread(key.hashCode());
    int binCount = 0;
}

// 作用:用高16位與低16位進(jìn)行^運(yùn)算,讓高位的數(shù)值可以進(jìn)行計(jì)算
// 為什么原來的高位沒有辦法計(jì)算呢?
// 我們后面的 (n - 1) & hash 的數(shù)據(jù),&數(shù)據(jù)如下:
// 00000000 00000000 00000000 01010101
// 0000000  00000000 00000000 00011111
// 我們這里看到,如果高16位不與低16位^運(yùn)算的話,那么基本我們的高位永遠(yuǎn)也參加不了計(jì)算
// 為什么需要&HASH_BITS:
// 保證最終的結(jié)果大于0,因?yàn)槿绻Y(jié)果小于0的話,代表不同的意義:
// static final int MOVED     = -1; // 代表當(dāng)前hash位置的數(shù)據(jù)正在擴(kuò)容!
// static final int TREEBIN   = -2; // 代表當(dāng)前hash位置下掛載的是一個(gè)紅黑樹
// static final int RESERVED  = -3; // 預(yù)留當(dāng)前索引位置……
static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

2.2 初始化數(shù)組

// tab指向table,標(biāo)準(zhǔn)的Doug Lea寫法
for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; 
    int n, i, fh;
    // 如果當(dāng)前的數(shù)組為空或者他的數(shù)組長(zhǎng)度為0
    // 則進(jìn)行初始化
    if (tab == null || (n = tab.length) == 0){
        tab = initTable();
    }
}


// sizeCtl:是數(shù)組在初始化和擴(kuò)容操作時(shí)的一個(gè)控制變量
// -1:代表當(dāng)前數(shù)組正在初始化
// 小于-1:低16位代表當(dāng)前數(shù)組正在擴(kuò)容的線程個(gè)數(shù)(如果1個(gè)線程擴(kuò)容,值為-2,如果2個(gè)線程擴(kuò)容,值為-3)
// 0:代表數(shù)組還沒初始化
// 大于0:代表當(dāng)前數(shù)組的擴(kuò)容閾值,或者是當(dāng)前數(shù)組的初始化大小
private final Node<K,V>[] initTable() {
    // 經(jīng)典引用
    Node<K,V>[] tab; 
    int sc;
    // 當(dāng)前的初始化沒有完成時(shí),會(huì)一直進(jìn)行該while循環(huán)
    while ((tab = table) == null || tab.length == 0) {
        // 如果小于0,代表當(dāng)前數(shù)組正在擴(kuò)容或者初始化
        // 當(dāng)前線程等待一下
        if ((sc = sizeCtl) < 0)
            Thread.yield(); 
        // 嘗試將SIZECTL從SC更改為-1
        // CAS修改,線程安全,保證只有一個(gè)線程執(zhí)行數(shù)組初始化
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                // 更改成功,再次判斷一下(參考DCL)
                // 防止下面sizeCtl = sc剛賦值完,正好有線程走到這一步,不做限制的話就會(huì)重新初始化了
                if ((tab = table) == null || tab.length == 0) {
                    // 判斷當(dāng)前的sc是否大于0(sc=SIZECTL)
                    // 大于0:n = sc
                    // 小于等于0:n = DEFAULT_CAPACITY
                    // 一般我們只要不傳入入?yún)?這里基本走DEFAULT_CAPACITY的擴(kuò)容
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    // 擴(kuò)容即可
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    // table、tab賦值
                    table = tab = nt;
                    // 這里有點(diǎn)意思
                    // 將sc賦值成(n - 1/4n) = 0.75N
                    // 這里0.75是不是很熟悉,負(fù)載因子
                    sc = n - (n >>> 2);
                }
            } finally {
                // 將上面的擴(kuò)容閾值賦予sizeCtl
                sizeCtl = sc;
            }
            // 結(jié)束循環(huán)
            break;
        }
    }
    return tab;
}

2.3 將數(shù)據(jù)插入到數(shù)組

// 如果當(dāng)前數(shù)組該下標(biāo)沒有數(shù)據(jù),直接插入即可
if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    // CAS將當(dāng)前的hash、key、value組裝成一個(gè)Node,插入當(dāng)前數(shù)組i位置
    // 插入成功結(jié)束即可
    if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))){
        break;
    }      
}

// 基于我們上面說的(n - 1) & hash算出下標(biāo)
// 返回當(dāng)前數(shù)組下該下標(biāo)的數(shù)據(jù)
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

static class Node<K,V> implements Map.Entry<K,V> {
    // 當(dāng)前的hash值
    final int hash;
    // key
    final K key;
    // value
    volatile V val;
    // 下一個(gè)節(jié)點(diǎn)(用來連鏈表的)
    volatile Node<K,V> next;
}

2.4 擴(kuò)容(后面聊)

  • 這部分后面聊
// 判斷當(dāng)前位置數(shù)據(jù)是否正在擴(kuò)容……
if ((fh = f.hash) == MOVED)
    // 如果在擴(kuò)容,則當(dāng)前線程幫助其擴(kuò)容
    tab = helpTransfer(tab, f);

2.5 將數(shù)據(jù)插入到鏈表

else {
    V oldVal = null;
    // 鎖當(dāng)前的數(shù)組下標(biāo)i的數(shù)組塊
    synchronized (f) {
        // 看一下當(dāng)前數(shù)組的i位置是不是等于f
        // 相當(dāng)于再次校驗(yàn)一次(DCL)
        if (tabAt(tab, i) == f) {
            // static final int MOVED     = -1; // 代表當(dāng)前hash位置的數(shù)據(jù)正在擴(kuò)容!
		   // static final int TREEBIN   = -2; // 代表當(dāng)前hash位置下掛載的是一個(gè)紅黑樹
		   // static final int RESERVED  = -3; // 預(yù)留當(dāng)前索引位置……
            // fh = f.hash
            // 判斷下當(dāng)前的fh是否大于0,也就是不是上面三種情況
            if (fh >= 0) {
                // 記錄當(dāng)前鏈表下面掛了幾個(gè)
                binCount = 1;
                // 獲取當(dāng)前的數(shù)組節(jié)點(diǎn),沒循環(huán)一次binCount加一
                for (Node<K,V> e = f;; ++binCount) {
                    K ek;
                    // 如果當(dāng)前數(shù)組下標(biāo)的hash和我們?nèi)雲(yún)⒌膆ash一樣,代表重復(fù)數(shù)據(jù)
                    if (e.hash == hash &&
                        // 如果當(dāng)前的key也等于原始的key
                        // 或者是根據(jù)equals判斷出來的相等(因?yàn)橛幸恍┛赡苤貙懥薳quals方法)
                        ((ek = e.key) == key ||
                         (ek != null && key.equals(ek)))) {
                        // 將當(dāng)前老的數(shù)據(jù)賦值給oldVal
                        oldVal = e.val;
                        // 這里的onlyIfAbsent我們之前也聊過
                        // 如果傳遞為false,代表key一致時(shí),直接覆蓋數(shù)據(jù)
   					  // 如果傳遞為true,代表key一致時(shí),什么都不做,key不存在,正常添加(Redis,setnx)
                        if (!onlyIfAbsent)
                            // 覆蓋數(shù)據(jù)
                            e.val = value;
                        break;
                    }
                    // 這里就不是相同的數(shù)據(jù)了,需要掛鏈表下面了
                    // 先獲取數(shù)組最上面的數(shù)據(jù)
                    Node<K,V> pred = e;
                    // 判斷下當(dāng)前的下一個(gè)數(shù)據(jù)是不是空指針
                    // 不是空指針的話,繼續(xù)指向下一個(gè)指針
                    if ((e = e.next) == null) {
                        // 直到最后的時(shí)候,創(chuàng)建一個(gè)節(jié)點(diǎn)掛上去
                        pred.next = new Node<K,V>(hash, key,value, null);
                        break;
                    }
                }
            }
}

2.6 將數(shù)據(jù)插入到紅黑樹

 // 如果上面不成立的話,也就是當(dāng)前的數(shù)組下面是一個(gè)紅黑樹
 // 需要將當(dāng)前的數(shù)據(jù)放到紅黑樹里面
else if (f instanceof TreeBin) {
    Node<K,V> p;
    binCount = 2;
    // 將當(dāng)前數(shù)據(jù)放入到紅黑樹中
    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
        // 記錄一下老數(shù)據(jù)
        oldVal = p.val;
        // 這里的onlyIfAbsent我們之前也聊過
        // 如果傳遞為false,代表key一致時(shí),直接覆蓋數(shù)據(jù)
        // 如果傳遞為true,代表key一致時(shí),什么都不做,key不存在,正常添加(Redis,setnx)
        if (!onlyIfAbsent)
            // 覆蓋數(shù)據(jù)
            p.val = value;
    }
}

2.7 鏈表轉(zhuǎn)紅黑樹

// 如果當(dāng)前的鏈表上的數(shù)據(jù)不等于0
if (binCount != 0) {
    // 當(dāng)前列表下的數(shù)據(jù)長(zhǎng)度大于8
    // 這里需要注意,大于8的話并不是立即轉(zhuǎn)成紅黑樹,還需要判斷當(dāng)前數(shù)組的長(zhǎng)度
    if (binCount >= TREEIFY_THRESHOLD)
        treeifyBin(tab, i);
    if (oldVal != null)
        return oldVal;
    break;
}
static final int TREEIFY_THRESHOLD = 8;
static final int MIN_TREEIFY_CAPACITY = 64;

private final void treeifyBin(Node<K,V>[] tab, int index) {
    Node<K,V> b; int n, sc;
    // 如果當(dāng)前的數(shù)組不為空
    if (tab != null) {
        // 如果當(dāng)前的數(shù)組長(zhǎng)度小于64,則沒必要轉(zhuǎn)成紅黑樹
        // 直接擴(kuò)容即可
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        // 后面的是轉(zhuǎn)成紅黑樹的代碼
        // 我們下一章在分析
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            synchronized (b) {
                if (tabAt(tab, index) == b) {
                    TreeNode<K,V> hd = null, tl = null;
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null);
                        if ((p.prev = tl) == null){
                            hd = p;
                        }
                        else{
                            tl.next = p;
                        }
                        tl = p;
                    }
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}

四、流程圖

1、初始化階段

2、存儲(chǔ)階段

五、總結(jié)

魯迅先生曾說:獨(dú)行難,眾行易,和志同道合的人一起進(jìn)步。彼此毫無保留的分享經(jīng)驗(yàn),才是對(duì)抗互聯(lián)網(wǎng)寒冬的最佳選擇。

其實(shí)很多時(shí)候,并不是我們不夠努力,很可能就是自己努力的方向不對(duì),如果有一個(gè)人能稍微指點(diǎn)你一下,你真的可能會(huì)少走幾年彎路。

以上就是淺析Java中ConcurrentHashMap的存儲(chǔ)流程的詳細(xì)內(nèi)容,更多關(guān)于ConcurrentHashMap存儲(chǔ)流程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java出現(xiàn)no XXX in java.library.path的解決及eclipse配置方式

    java出現(xiàn)no XXX in java.library.path的解決及eclipse配

    這篇文章主要介紹了java出現(xiàn)no XXX in java.library.path的解決及eclipse配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • SpringBoot SSO輕松實(shí)現(xiàn)(附demo)

    SpringBoot SSO輕松實(shí)現(xiàn)(附demo)

    這篇文章主要介紹了SpringBoot SSO輕松實(shí)現(xiàn)(附demo),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Myeclipse部署Tomcat_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Myeclipse部署Tomcat_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    這篇文章給大家介紹了Myeclipse部署Tomcat的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧
    2017-07-07
  • Java文件讀取寫入后 md5值不變的實(shí)現(xiàn)方法

    Java文件讀取寫入后 md5值不變的實(shí)現(xiàn)方法

    下面小編就為大家分享一篇Java文件讀取寫入后 md5值不變的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助
    2017-11-11
  • ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法

    ReadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法

    下面小編就為大家?guī)硪黄猂eadWriteLock接口及其實(shí)現(xiàn)ReentrantReadWriteLock方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-06-06
  • Spring?Boot?@Autowired?@Resource屬性賦值時(shí)機(jī)探究

    Spring?Boot?@Autowired?@Resource屬性賦值時(shí)機(jī)探究

    這篇文章主要為大家介紹了Spring?Boot?@Autowired?@Resource屬性賦值時(shí)機(jī),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • SpringBoot靜態(tài)資源css,js,img配置方案

    SpringBoot靜態(tài)資源css,js,img配置方案

    這篇文章主要介紹了SpringBoot靜態(tài)資源css,js,img配置方案,下文給大家分享了三種解決方案,需要的朋友可以參考下
    2017-07-07
  • 關(guān)于SpringBoot禁止循環(huán)依賴解說

    關(guān)于SpringBoot禁止循環(huán)依賴解說

    這篇文章主要介紹了關(guān)于SpringBoot禁止循環(huán)依賴解說,Spring的Bean管理,文章圍繞主題展開詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-05-05
  • maven?傳遞依賴的實(shí)現(xiàn)

    maven?傳遞依賴的實(shí)現(xiàn)

    本文主要介紹了Maven中傳遞依賴的處理方式,如scope=compile影響依賴傳遞,使用排除不需要的依賴,以及如何通過查看依賴樹解決運(yùn)行時(shí)錯(cuò)誤,特別是在依賴排除后可能導(dǎo)致的運(yùn)行時(shí)缺少必需包的問題,感興趣的可以了解一下
    2024-10-10
  • Java中Elasticsearch 實(shí)現(xiàn)分頁方式(三種方式)

    Java中Elasticsearch 實(shí)現(xiàn)分頁方式(三種方式)

    Elasticsearch是用Java語言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級(jí)搜索引擎,這篇文章主要介紹了Elasticsearch實(shí)現(xiàn)分頁的3種方式,需要的朋友可以參考下
    2022-07-07

最新評(píng)論

汉川市| 社旗县| 桐梓县| 大丰市| 都安| 宜州市| 桐梓县| 霍林郭勒市| 调兵山市| 乐都县| 隆回县| 合山市| 黔东| 绥芬河市| 恩平市| 个旧市| 峨边| 漳浦县| 高邑县| 屏东市| 盐边县| 麦盖提县| 乡宁县| 双辽市| 德清县| 茌平县| 汶上县| 漠河县| 顺义区| 瑞丽市| 邵阳市| 吉安市| 柞水县| 阿图什市| 贵溪市| 呼和浩特市| 景德镇市| 大同县| 班戈县| 晋中市| 淮阳县|