成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

[學(xué)習(xí)筆記-Java集合-8] Map - ConcurrentHashMap 源碼分析(二)

2501207950 / 1702人閱讀

摘要:那么,如果之后不是簡(jiǎn)單的操作,而是還有其它業(yè)務(wù)操作,之后才是,比如下面這樣,這該怎么辦呢其它業(yè)務(wù)操作這時(shí)候就沒(méi)辦法使用提供的方法了,只能業(yè)務(wù)自己來(lái)保證線程安全了,比如下面這樣其它業(yè)務(wù)操作這樣雖然不太友好,但是最起碼能保證業(yè)務(wù)邏輯是正確的。

刪除元素

刪除元素跟添加元素一樣,都是先找到元素所在的桶,然后采用分段鎖的思想鎖住整個(gè)桶,再進(jìn)行操作。

public V remove(Object key) {
    // 調(diào)用替換節(jié)點(diǎn)方法
    return replaceNode(key, null, null);
}

final V replaceNode(Object key, V value, Object cv) {
    // 計(jì)算hash
    int hash = spread(key.hashCode());
    // 自旋
    for (Node[] tab = table;;) {
        Node f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
            // 如果目標(biāo)key所在的桶不存在,跳出循環(huán)返回null
            break;
        else if ((fh = f.hash) == MOVED)
            // 如果正在擴(kuò)容中,協(xié)助擴(kuò)容
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            // 標(biāo)記是否處理過(guò)
            boolean validated = false;
            synchronized (f) {
                // 再次驗(yàn)證當(dāng)前桶第一個(gè)元素是否被修改過(guò)
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        // fh>=0表示是鏈表節(jié)點(diǎn)
                        validated = true;
                        // 遍歷鏈表尋找目標(biāo)節(jié)點(diǎn)
                        for (Node e = f, pred = null;;) {
                            K ek;
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                // 找到了目標(biāo)節(jié)點(diǎn)
                                V ev = e.val;
                                // 檢查目標(biāo)節(jié)點(diǎn)舊value是否等于cv
                                if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                    oldVal = ev;
                                    if (value != null)
                                        // 如果value不為空則替換舊值
                                        e.val = value;
                                    else if (pred != null)
                                        // 如果前置節(jié)點(diǎn)不為空
                                        // 刪除當(dāng)前節(jié)點(diǎn)
                                        pred.next = e.next;
                                    else
                                        // 如果前置節(jié)點(diǎn)為空
                                        // 說(shuō)明是桶中第一個(gè)元素,刪除之
                                        setTabAt(tab, i, e.next);
                                }
                                break;
                            }
                            pred = e;
                            // 遍歷到鏈表尾部還沒(méi)找到元素,跳出循環(huán)
                            if ((e = e.next) == null)
                                break;
                        }
                    }
                    else if (f instanceof TreeBin) {
                        // 如果是樹(shù)節(jié)點(diǎn)
                        validated = true;
                        TreeBin t = (TreeBin)f;
                        TreeNode r, p;
                        // 遍歷樹(shù)找到了目標(biāo)節(jié)點(diǎn)
                        if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                            V pv = p.val;
                            // 檢查目標(biāo)節(jié)點(diǎn)舊value是否等于cv
                            if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                oldVal = pv;
                                if (value != null)
                                    // 如果value不為空則替換舊值
                                    p.val = value;
                                else if (t.removeTreeNode(p))
                                    // 如果value為空則刪除元素
                                    // 如果刪除后樹(shù)的元素個(gè)數(shù)較少則退化成鏈表
                                    // t.removeTreeNode(p)這個(gè)方法返回true表示刪除節(jié)點(diǎn)后樹(shù)的元素個(gè)數(shù)較少
                                    setTabAt(tab, i, untreeify(t.first));
                            }
                        }
                    }
                }
            }
            // 如果處理過(guò),不管有沒(méi)有找到元素都返回
            if (validated) {
                // 如果找到了元素,返回其舊值
                if (oldVal != null) {
                    // 如果要替換的值為空,元素個(gè)數(shù)減1
                    if (value == null)
                        addCount(-1L, -1);
                    return oldVal;
                }
                break;
            }
        }
    }
    // 沒(méi)找到元素返回空
    return null;
}

計(jì)算hash;

如果所在的桶不存在,表示沒(méi)有找到目標(biāo)元素,返回;

如果正在擴(kuò)容,則協(xié)助擴(kuò)容完成后再進(jìn)行刪除操作;

如果是以鏈表形式存儲(chǔ)的,則遍歷整個(gè)鏈表查找元素,找到之后再刪除;

如果是以樹(shù)形式存儲(chǔ)的,則遍歷樹(shù)查找元素,找到之后再刪除;

如果是以樹(shù)形式存儲(chǔ)的,刪除元素之后樹(shù)較小,則退化成鏈表;

如果確實(shí)刪除了元素,則整個(gè)map元素個(gè)數(shù)減1,并返回舊值;

如果沒(méi)有刪除元素,則返回null;

獲取元素

獲取元素,根據(jù)目標(biāo)key所在桶的第一個(gè)元素的不同采用不同的方式獲取元素,關(guān)鍵點(diǎn)在于find()方法的重寫(xiě)。

public V get(Object key) {
    Node[] tab; Node e, p; int n, eh; K ek;
    // 計(jì)算hash
    int h = spread(key.hashCode());
    // 如果元素所在的桶存在且里面有元素
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
        // 如果第一個(gè)元素就是要找的元素,直接返回
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        else if (eh < 0)
            // hash小于0,說(shuō)明是樹(shù)或者正在擴(kuò)容
            // 使用find尋找元素,find的尋找方式依據(jù)Node的不同子類(lèi)有不同的實(shí)現(xiàn)方式
            return (p = e.find(h, key)) != null ? p.val : null;

        // 遍歷整個(gè)鏈表尋找元素
        while ((e = e.next) != null) {
            if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

hash到元素所在的桶;

如果桶中第一個(gè)元素就是該找的元素,直接返回;

如果是樹(shù)或者正在遷移元素,則調(diào)用各自Node子類(lèi)的find()方法尋找元素;

如果是鏈表,遍歷整個(gè)鏈表尋找元素;

獲取元素沒(méi)有加鎖;

獲取元素個(gè)數(shù)

元素個(gè)數(shù)的存儲(chǔ)也是采用分段的思想,獲取元素個(gè)數(shù)時(shí)需要把所有段加起來(lái)。

public int size() {
    // 調(diào)用sumCount()計(jì)算元素個(gè)數(shù)
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
                    (int)n);
}

final long sumCount() {
    // 計(jì)算CounterCell所有段及baseCount的數(shù)量之和
    CounterCell[] as = counterCells; CounterCell a;
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

元素的個(gè)數(shù)依據(jù)不同的線程存在在不同的段里;(見(jiàn)addCounter()分析)

計(jì)算CounterCell所有段及baseCount的數(shù)量之和;

獲取元素個(gè)數(shù)沒(méi)有加鎖;

總結(jié)

(1)ConcurrentHashMap是HashMap的線程安全版本;

(2)ConcurrentHashMap采用(數(shù)組 + 鏈表 + 紅黑樹(shù))的結(jié)構(gòu)存儲(chǔ)元素;

(3)ConcurrentHashMap相比于同樣線程安全的HashTable,效率要高很多;

(4)ConcurrentHashMap采用的鎖有 synchronized,CAS,自旋鎖,分段鎖,volatile等;

(5)ConcurrentHashMap中沒(méi)有threshold和loadFactor這兩個(gè)字段,而是采用sizeCtl來(lái)控制;

(6)sizeCtl = -1,表示正在進(jìn)行初始化;

(7)sizeCtl = 0,默認(rèn)值,表示后續(xù)在真正初始化的時(shí)候使用默認(rèn)容量;

(8)sizeCtl > 0,在初始化之前存儲(chǔ)的是傳入的容量,在初始化或擴(kuò)容后存儲(chǔ)的是下一次的擴(kuò)容門(mén)檻;

(9)sizeCtl = (resizeStamp << 16) + (1 + nThreads),表示正在進(jìn)行擴(kuò)容,高位存儲(chǔ)擴(kuò)容郵戳,低位存儲(chǔ)擴(kuò)容線程數(shù)加1;

(10)更新操作時(shí)如果正在進(jìn)行擴(kuò)容,當(dāng)前線程協(xié)助擴(kuò)容;

(11)更新操作會(huì)采用synchronized鎖住當(dāng)前桶的第一個(gè)元素,這是分段鎖的思想;

(12)整個(gè)擴(kuò)容過(guò)程都是通過(guò)CAS控制sizeCtl這個(gè)字段來(lái)進(jìn)行的,這很關(guān)鍵;

(13)遷移完元素的桶會(huì)放置一個(gè)ForwardingNode節(jié)點(diǎn),以標(biāo)識(shí)該桶遷移完畢;

(14)元素個(gè)數(shù)的存儲(chǔ)也是采用的分段思想,類(lèi)似于LongAdder的實(shí)現(xiàn);

(15)元素個(gè)數(shù)的更新會(huì)把不同的線程hash到不同的段上,減少資源爭(zhēng)用;

(16)元素個(gè)數(shù)的更新如果還是出現(xiàn)多個(gè)線程同時(shí)更新一個(gè)段,則會(huì)擴(kuò)容段(CounterCell);

(17)獲取元素個(gè)數(shù)是把所有的段(包括baseCount和CounterCell)相加起來(lái)得到的;

(18)查詢(xún)操作是不會(huì)加鎖的,所以ConcurrentHashMap不是強(qiáng)一致性的;

(19)ConcurrentHashMap中不能存儲(chǔ)key或value為null的元素;

學(xué)習(xí)重點(diǎn)

ConcurrentHashMap中值得學(xué)習(xí)的技術(shù)

(1)CAS + 自旋,樂(lè)觀鎖的思想,減少線程上下文切換的時(shí)間;

(2)分段鎖的思想,減少同一把鎖爭(zhēng)用帶來(lái)的低效問(wèn)題;

(3)CounterCell,分段存儲(chǔ)元素個(gè)數(shù),減少多線程同時(shí)更新一個(gè)字段帶來(lái)的低效;

(4)@sun.misc.Contended(CounterCell上的注解),避免偽共享;

(5)多線程協(xié)同進(jìn)行擴(kuò)容;

ConcurrentHashMap 并發(fā)下使用問(wèn)題

看下面的使用例子:

private static final Map map = new ConcurrentHashMap<>();

public void unsafeUpdate(Integer key, Integer value) {
    Integer oldValue = map.get(key);
    if (oldValue == null) {
        map.put(key, value);
    }
}

如果有多個(gè)線程同時(shí)調(diào)用unsafeUpdate()這個(gè)方法,ConcurrentHashMap是無(wú)法保證線程安全的。

因?yàn)間et()之后if之前可能有其它線程已經(jīng)put()了這個(gè)元素,這時(shí)候再put()就把那個(gè)線程put()的元素覆蓋了。

那怎么修改呢?

使用putIfAbsent()方法,它會(huì)保證元素不存在時(shí)才插入元素,如下:

public void safeUpdate(Integer key, Integer value) {
    map.putIfAbsent(key, value);
}

那么,如果上面oldValue不是跟null比較,而是跟一個(gè)特定的值比如1進(jìn)行比較怎么辦?也就是下面這樣:

public void unsafeUpdate(Integer key, Integer value) {
    Integer oldValue = map.get(key);
    if (oldValue == 1) {
        map.put(key, value);
    }
}

這樣的話就沒(méi)辦法使用putIfAbsent()方法了。

其實(shí),ConcurrentHashMap還提供了另一個(gè)方法叫replace(K key, V oldValue, V newValue)可以解決這個(gè)問(wèn)題。

replace(K key, V oldValue, V newValue)這個(gè)方法可不能亂用,如果傳入的newValue是null,則會(huì)刪除元素。

public void safeUpdate(Integer key, Integer value) {
    map.replace(key, 1, value);
}

那么,如果if之后不是簡(jiǎn)單的put()操作,而是還有其它業(yè)務(wù)操作,之后才是put(),比如下面這樣,這該怎么辦呢?

public void unsafeUpdate(Integer key, Integer value) {
    Integer oldValue = map.get(key);
    if (oldValue == 1) {
        System.out.println(System.currentTimeMillis());
        /**
         * 其它業(yè)務(wù)操作
         */
        System.out.println(System.currentTimeMillis());

        map.put(key, value);
    }
}

這時(shí)候就沒(méi)辦法使用ConcurrentHashMap提供的方法了,只能業(yè)務(wù)自己來(lái)保證線程安全了,比如下面這樣:

public void safeUpdate(Integer key, Integer value) {
    synchronized (map) {
        Integer oldValue = map.get(key);
        if (oldValue == null) {
            System.out.println(System.currentTimeMillis());
            /**
             * 其它業(yè)務(wù)操作
             */
            System.out.println(System.currentTimeMillis());

            map.put(key, value);
        }
    }
}

這樣雖然不太友好,但是最起碼能保證業(yè)務(wù)邏輯是正確的。
當(dāng)然,這里使用ConcurrentHashMap的意義也就不大了,可以換成普通的HashMap了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/76238.html

相關(guān)文章

  • [學(xué)習(xí)筆記-Java集合-7] Map - ConcurrentHashMap 源碼分析(一)

    摘要:簡(jiǎn)介是的線程安全版本,內(nèi)部也是使用數(shù)組鏈表紅黑樹(shù)的結(jié)構(gòu)來(lái)存儲(chǔ)元素。相比于同樣線程安全的來(lái)說(shuō),效率等各方面都有極大地提高。中的關(guān)鍵字,內(nèi)部實(shí)現(xiàn)為監(jiān)視器鎖,主要是通過(guò)對(duì)象監(jiān)視器在對(duì)象頭中的字段來(lái)表明的。 簡(jiǎn)介 ConcurrentHashMap是HashMap的線程安全版本,內(nèi)部也是使用(數(shù)組 + 鏈表 + 紅黑樹(shù))的結(jié)構(gòu)來(lái)存儲(chǔ)元素。 相比于同樣線程安全的HashTable來(lái)說(shuō),效率等各方...

    SoapEye 評(píng)論0 收藏0
  • 一文掌握關(guān)于Java數(shù)據(jù)結(jié)構(gòu)所有知識(shí)點(diǎn)(歡迎一起完善)

    摘要:是棧,它繼承于。滿二叉樹(shù)除了葉結(jié)點(diǎn)外每一個(gè)結(jié)點(diǎn)都有左右子葉且葉子結(jié)點(diǎn)都處在最底層的二叉樹(shù)。沒(méi)有鍵值相等的節(jié)點(diǎn)。這是數(shù)據(jù)庫(kù)選用樹(shù)的最主要原因。 在我們學(xué)習(xí)Java的時(shí)候,很多人會(huì)面臨我不知道繼續(xù)學(xué)什么或者面試會(huì)問(wèn)什么的尷尬情況(我本人之前就很迷茫)。所以,我決定通過(guò)這個(gè)開(kāi)源平臺(tái)來(lái)幫助一些有需要的人,通過(guò)下面的內(nèi)容,你會(huì)掌握系統(tǒng)的Java學(xué)習(xí)以及面試的相關(guān)知識(shí)。本來(lái)是想通過(guò)Gitbook的...

    keithxiaoy 評(píng)論0 收藏0
  • Java 容器學(xué)習(xí)之 HashMap

    摘要:底層的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組鏈表紅黑樹(shù),紅黑樹(shù)是在中加進(jìn)來(lái)的。負(fù)載因子哈希表中的填滿程度。 前言 把 Java 容器的學(xué)習(xí)筆記放到 github 里了,還在更新~其他的目前不打算抽出來(lái)作為文章寫(xiě),感覺(jué)挖的還不夠深,等對(duì)某些東西理解的更深了再寫(xiě)文章吧Java 容器目錄如下: Java 容器 一、概述 二、源碼學(xué)習(xí) 1. Map 1.1 HashMap 1.2 LinkedHashM...

    Alex 評(píng)論0 收藏0
  • Java相關(guān)

    摘要:本文是作者自己對(duì)中線程的狀態(tài)線程間協(xié)作相關(guān)使用的理解與總結(jié),不對(duì)之處,望指出,共勉。當(dāng)中的的數(shù)目而不是已占用的位置數(shù)大于集合番一文通版集合番一文通版垃圾回收機(jī)制講得很透徹,深入淺出。 一小時(shí)搞明白自定義注解 Annotation(注解)就是 Java 提供了一種元程序中的元素關(guān)聯(lián)任何信息和著任何元數(shù)據(jù)(metadata)的途徑和方法。Annotion(注解) 是一個(gè)接口,程序可以通過(guò)...

    wangtdgoodluck 評(píng)論0 收藏0
  • 這幾道Java集合框架面試題在面試中幾乎必問(wèn)

    摘要:若遇到哈希沖突,則將沖突的值加到鏈表中即可。之后相比于之前的版本,之后在解決哈希沖突時(shí)有了較大的變化,當(dāng)鏈表長(zhǎng)度大于閾值默認(rèn)為時(shí),將鏈表轉(zhuǎn)化為紅黑樹(shù),以減少搜索時(shí)間。有序,唯一紅黑樹(shù)自平衡的排序二叉樹(shù)。 本文是最最最常見(jiàn)Java面試題總結(jié)系列第三周的文章。主要內(nèi)容: Arraylist 與 LinkedList 異同 ArrayList 與 Vector 區(qū)別 HashMap的底層...

    bigdevil_s 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<