91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ConcurrentHashMap在Java7和中的異同點是怎樣的

發布時間:2021-11-20 14:53:45 來源:億速云 閱讀:149 作者:柒染 欄目:云計算

本篇文章為大家展示了ConcurrentHashMap在Java7和中的異同點是怎樣的,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

在 Java 8 中,對于 ConcurrentHashMap 這個常用的工具類進行了很大的升級,對比之前 Java 7 版本在諸多方面都進行了調整和變化。

一:Java 7 版本的 ConcurrentHashMap

ConcurrentHashMap在Java7和中的異同點是怎樣的

從圖中我們可以看出,在 ConcurrentHashMap 內部進行了 Segment 分段,Segment 繼承了 ReentrantLock,可以理解為一把鎖,各個 Segment 之間都是相互獨立上鎖的,互不影響。相比于之前的 Hashtable 每次操作都需要把整個對象鎖住而言,大大提高了并發效率。因為它的鎖與鎖之間是獨立的,而不是整個對象只有一把鎖。

每個 Segment 的底層數據結構與 HashMap 類似,仍然是數組和鏈表組成的拉鏈法結構。默認有 0~15 共 16 個 Segment,所以最多可以同時支持 16 個線程并發操作(操作分別分布在不同的 Segment 上)。這個默認值16 可以在初始化的時候設置為其他值,但是一旦確認初始化以后,是不可以擴容的。

二:Java 8 版本的 ConcurrentHashMap

java 8 中,幾乎完全重寫了 ConcurrentHashMap,代碼量從原來 Java 7 中的 1000 多行,變成了現在的 6000 多行,所以也大大提高了源碼的閱讀難度。

ConcurrentHashMap在Java7和中的異同點是怎樣的

圖中的節點有三種類型。

  • 第一種是最簡單的,空著的位置代表當前還沒有元素來填充。

  • 第二種就是和 HashMap 非常類似的拉鏈法結構,在每一個槽中會首先填入第一個節點,但是后續如果計算出相同的 Hash - 值,就用鏈表的形式往后進行延伸。

  • 第三種結構就是紅黑樹結構,這是 Java 7 的 ConcurrentHashMap 中所沒有的結構。

當第二種情況的鏈表長度大于某一個閾值(默認為 8),且同時滿足一定的容量要求的時候,ConcurrentHashMap 便會把這個鏈表從鏈表的形式轉化為紅黑樹的形式,目的是進一步提高它的查找性能。

由于自平衡的特點,即左右子樹高度幾乎一致,所以其查找性能近似于二分查找,時間復雜度是 O(log(n)) 級別;反觀鏈表,它的時間復雜度就不一樣了,如果發生了最壞的情況,可能需要遍歷整個鏈表才能找到目標元素,時間復雜度為 O(n),遠遠大于紅黑樹的 O(log(n)),尤其是在節點越來越多的情況下,O(log(n)) 體現出的優勢會更加明顯。

ConcurrentHashMap 引入紅黑樹,好處就是避免在極端的情況下沖突鏈表變得很長,在查詢的時候,效率會非常慢。而紅黑樹具有自平衡的特點,所以,即便是極端情況下,也可以保證查詢效率在 O(log(n))

2.2 Java 8 版本的 ConcurrentHashMap 的重要源碼

下面我們深入源碼分析。由于 Java 7 版本已經過時了,所以我們把重點放在 Java 8 版本的源碼分析上。

2.1.1 Node 節點

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        //..........
}

每個 Node 里面是 key-value 的形式,并且把 value 用 volatile 修飾,以便保證可見性,同時內部還有一個指向下一個節點的 next 指針,方便產生鏈表結構。

2.1.2 put 方法源碼分析

put 方法的核心是 putVal 方法:

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) {
        throw new NullPointerException();
    }
    //計算 hash 值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K, V>[] tab = table; ; ) {
        Node<K, V> f;
        int n, i, fh;
        //如果數組是空的,就進行初始化
        if (tab == null || (n = tab.length) == 0) {
            tab = initTable();
        }
        // 找該 hash 值對應的數組下標
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //如果該位置是空的,就用 CAS 的方式放入新值
            if (casTabAt(tab, i, null,
                    new Node<K, V>(hash, key, value, null))) {
                break;
            }
        }
        //hash值等于 MOVED 代表在擴容
        else if ((fh = f.hash) == MOVED) {
            tab = helpTransfer(tab, f);
        }
        //槽點上是有值的情況
        else {
            V oldVal = null;
            //用 synchronized 鎖住當前槽點,保證并發安全
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    //如果是鏈表的形式
                    if (fh >= 0) {
                        binCount = 1;
                        //遍歷鏈表
                        for (Node<K, V> e = f; ; ++binCount) {
                            K ek;
                            //如果發現該 key 已存在,就判斷是否需要進行覆蓋,然后返回
                            if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                            (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent) {
                                    e.val = value;
                                }
                                break;
                            }
                            Node<K, V> pred = e;
                            //到了鏈表的尾部也沒有發現該 key,說明之前不存在,就把新值添加到鏈表的最后
                            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;
                        //調用 putTreeVal 方法往紅黑樹里增加數據
                        if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key,
                                value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent) {
                                p.val = value;
                            }
                        }
                    }
                }
            }
            if (binCount != 0) {
                //檢查是否滿足條件并把鏈表轉換為紅黑樹的形式,默認的 TREEIFY_THRESHOLD 閾值是 8
                if (binCount >= TREEIFY_THRESHOLD) {
                    treeifyBin(tab, i);
                }
                //putVal 的返回是添加前的舊值,所以返回 oldVal
                if (oldVal != null) {
                    return oldVal;
                }
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

可以看出,方法中會逐步根據當前槽點是否初始化、空、擴容、鏈表、紅黑樹等不同情況做出不同的處理。

2.1.3 get 方法源碼分析

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    //計算 hash 值
    int h = spread(key.hashCode());
    //如果整個數組是空的,或者當前槽點的數據是空的,說明 key 對應的 value 不存在,直接返回 null
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
        //判斷頭結點是否就是我們需要的節點,如果是則直接返回
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //如果頭結點 hash 值小于 0,說明是紅黑樹或者正在擴容,就用對應的 find 方法來查找
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        //遍歷鏈表來查找
        while ((e = e.next) != null) {
            if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}
  1. 計算 Hash 值,并由此值找到對應的槽點;

  2. 如果數組是空的或者該位置為 null,那么直接返回 null 就可以了;

  3. 如果該位置處的節點剛好就是我們需要的,直接返回該節點的值;

  4. 如果該位置節點是紅黑樹或者正在擴容,就用 find 方法繼續查找;

  5. 否則那就是鏈表,就進行遍歷鏈表查找。

三:對比Java7 和Java8 的異同和優缺點

3.1 數據結構

Java 7 采用 Segment 分段鎖來實現,而 Java 8 中的 ConcurrentHashMap 使用數組 + 鏈表 + 紅黑樹。

ConcurrentHashMap在Java7和中的異同點是怎樣的

ConcurrentHashMap在Java7和中的異同點是怎樣的

3.2 并發度

Java 7 中,每個 Segment 獨立加鎖,最大并發個數就是 Segment 的個數,默認是 16。

但是到了 Java 8 中,鎖粒度更細,理想情況下 table 數組元素的個數(也就是數組長度)就是其支持并發的最大個數,并發度比之前有提高。

3.3 保證并發安全的原理

java 7 采用 Segment 分段鎖來保證安全,而 Segment 是繼承自 ReentrantLock。

Java 8 中放棄了 Segment 的設計,采用 Node + CAS + synchronized 保證線程安全。

3.4 遇到 Hash 碰撞

Java 7 在 Hash 沖突時,會使用拉鏈法,也就是鏈表的形式。

Java 8 先使用拉鏈法,在鏈表長度超過一定閾值時,將鏈表轉換為紅黑樹,來提高查找效率。

3.5 查詢時間復雜度

Java 7 遍歷鏈表的時間復雜度是 O(n),n 為鏈表長度。

Java 8 如果變成遍歷紅黑樹,那么時間復雜度降低為 O(log(n)),n 為樹的節點個數。

上述內容就是ConcurrentHashMap在Java7和中的異同點是怎樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

屏南县| 沅江市| 红河县| 齐齐哈尔市| 阳朔县| 沈丘县| 芜湖市| 晋州市| 张家川| 连江县| 张北县| 邻水| 土默特左旗| 黎城县| 九龙县| 浙江省| 西乡县| 宿迁市| 广元市| 西乌珠穆沁旗| 古交市| 望城县| 开鲁县| 大余县| 衡阳市| 荣昌县| 南江县| 易门县| 定安县| 五家渠市| 丰台区| 于都县| 鹿邑县| 安远县| 和顺县| 油尖旺区| 天峻县| 正安县| 彭泽县| 黄龙县| 唐河县|