源碼閱讀——ConcurrentHashMap

先看問題:

  • ConcurrentHashMap是怎么做到線程安全的?
  • get方法如何線程安全地獲取key、value?
  • put方法如何線程安全地設(shè)置key、value?
  • size方法如果線程安全地獲取容器容量?
  • 底層數(shù)據(jù)結(jié)構(gòu)擴(kuò)容時(shí)如果保證線程安全?
  • 初始化數(shù)據(jù)結(jié)構(gòu)時(shí)如果保證線程安全?
    volatile變量(sizeCtl):它是一個(gè)標(biāo)記位,用來告訴其他線程這個(gè)坑位 有沒有人在,其線程間的可見性由volatile保證。
    CAS操作:CAS操作保證了設(shè)置sizeCtl標(biāo)記位的原子性,保證了只有一個(gè)線程能設(shè)置成功
  • ConcurrentHashMap并發(fā)效率是如何提高的?
  • 和加鎖相比較,為什么它比HashTable效率高?
//node的定義
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        // volatile 關(guān)鍵字修飾
        volatile V val;
        volatile Node<K,V> next;

public V put(K key, V value) {
        return putVal(key, value, false);
    }

 /** Implementation for put and putIfAbsent */
 final V putVal(K key, V value, boolean onlyIfAbsent) {
        //key 或 value都不能為空
        //這是因?yàn)楫?dāng)通過 get(k) 獲取對應(yīng)的 value 時(shí),如果獲取到的是 null 時(shí),無法判斷,它是 put(k,v) 的時(shí)候 value 為 null,還是這個(gè) key 從來沒有添加。
        //**1.這個(gè)key從來沒有在map中映射過。
        //**2.這個(gè)key的value在設(shè)置的時(shí)候,就是null。
        //在非線程安全的map集合(HashMap)中可以使用map.contains(key)方法來判斷,而ConcurrentHashMap卻不可以。hashmap正確使用場景是單線程下,由于是單線程,當(dāng)?shù)玫降膙alue是null的時(shí)候,可以用hashMap.containsKey(key)方法來區(qū)分上面說的兩重含義(保證在單線程下 線程安全 不存在并發(fā)場景,所以不會(huì)存在二義性)。
        if (key == null || value == null) throw new NullPointerException();
        //獲取hashcode
        int hash = spread(key.hashCode());
        int binCount = 0;
        
        //遍歷數(shù)組中的node
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            
            //Node數(shù)組為空,初始化Node數(shù)組
            if (tab == null || (n = tab.length) == 0)
                //看下面有詳細(xì)解釋
                tab = initTable();
            
            //該node節(jié)點(diǎn)中沒有元素,CAS對該位置的節(jié)點(diǎn)進(jìn)行原子操作
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                     //插入成功,退出循環(huán)
                    break;                   // no lock when adding to empty bin
            }
            //如果Node的hash值等于-1,map進(jìn)行擴(kuò)容
            else if ((fh = f.hash) == MOVED)
                //幫助擴(kuò)容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //鎖的是數(shù)組槽里面的node
                synchronized (f) {
                    //二次確認(rèn)此Node對象還是原來的那一個(gè)
                    //其使用Unsafe類volatile的操作volatile式地查看值,保證每次獲取到的值都是最新的:
                    if (tabAt(tab, i) == f) {
                        //為什么大于等于0是代表的鏈表,小于0代表是紅黑樹?
                        //定義的常量 static final int TREEBIN   = -2; // hash for roots of trees
                        //構(gòu)造函數(shù)里面調(diào)用super,會(huì)將-2作為hash
                        //感覺沒必要,直接像hashMap一樣先判斷是否紅黑樹,然后else里面處理鏈表邏輯
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //這個(gè)if是判斷在鏈表上是否存在key相同,若相同,則根據(jù)onlyIfAbsent的結(jié)果考慮是否進(jìn)行替換
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                //感覺沒必要申明這個(gè)對象,下面直接用e.next.next就可以了
                                Node<K,V> pred = e;
                                //尾插法,插入節(jié)點(diǎn)
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

// 初始化數(shù)組
 private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            //sizeCtl是一個(gè)標(biāo)記位,若為-1也就是小于0,代表有線程在進(jìn)行初始化工作了
            if ((sc = sizeCtl) < 0)
                //讓出CPU時(shí)間片
                Thread.yield(); // lost initialization race; just spin
            //CAS操作,將本實(shí)例的sizeCtl變量設(shè)置為-1,類似上個(gè)樂觀鎖,讓別人知道我在開始初始化數(shù)組了
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                //再檢查一遍數(shù)組是否為空
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        //Node數(shù)組
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        //通過位運(yùn)算,n減去n二進(jìn)制右移2位,相當(dāng)于乘以0.75
                        //例如16經(jīng)過運(yùn)算為12,與乘0.75一樣,只不過位運(yùn)算更快
                        sc = n - (n >>> 2);
                    }
                } finally {
                     //將計(jì)算后的sc(12)直接賦值給sizeCtl,表示達(dá)到12長度就擴(kuò)容
                     //由于這里只會(huì)有一個(gè)線程在執(zhí)行,直接賦值即可,沒有線程安全問題
                    //只需要保證可見性
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

https://mp.weixin.qq.com/s/6FeG73dG44GYPig51vr_5g

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容