Hashtable,Collections.SynchronizedMap和ConcurrentHashMap線程安全實(shí)現(xiàn)原理的區(qū)別以及性能測(cè)試

Hashtable,Collections.SynchronizedMap和ConcurrentHashMap線程安全實(shí)現(xiàn)原理的區(qū)別以及性能測(cè)試

這三種 Map 都是 Java 中比較重要的集合類,雖然前兩個(gè)不太常用,但是因?yàn)榕c多線程相關(guān),所以關(guān)于這幾種 Map 的對(duì)比已經(jīng)成為了 Java 面試時(shí)的高頻考點(diǎn)。首先要說(shuō)明的是,其中每一個(gè)單獨(dú)拎出來(lái)都足夠支撐一篇長(zhǎng)篇大論的技術(shù)文章,所以本文把重點(diǎn)放在了這三種集合類的線程安全實(shí)現(xiàn)原理的對(duì)比以及性能測(cè)試上,其他細(xì)節(jié)不做深入探討。

一、線程安全原理對(duì)比

1. Hashtable

首先必須吐槽一下這個(gè)類名,作為官方工具類竟然不符合駝峰命名規(guī)則,怪不得被棄用了,開(kāi)玩笑哈哈,主要原因還是性能低下,那 Hashtable 的性能為什么低下呢,這個(gè)嘛只需要看一下它的源碼就一目了然了,以下是 Hashtable 中幾個(gè)比較重要的方法:

public synchronized V put(K key, V value) {...}
public synchronized V get(Object key) {...}
public synchronized int size() {...}
public synchronized boolean remove(Object key, Object value) {...}
public synchronized boolean contains(Object value) {...}
... ...

查看源碼后可以看出,Hashtable 實(shí)現(xiàn)線程安全的原理相當(dāng)簡(jiǎn)單粗暴,直接在方法聲明上使用 synchronized 關(guān)鍵字。這樣一來(lái),不管線程執(zhí)行哪個(gè)方法,即便只是讀取數(shù)據(jù),都需要鎖住整個(gè) Hashtable 對(duì)象,可想而知其并發(fā)性能必然不會(huì)太好。

2. Collections.SynchronizedMap

SynchronizedMapCollections 集合類的私有靜態(tài)內(nèi)部類,其定義和構(gòu)造方法如下:

private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;
          // 用于接收傳入的Map對(duì)象,也是類方法操作的對(duì)象
        private final Map<K,V> m;     
          // 鎖對(duì)象
        final Object mutex;   
  
            // 以下是SynchronizedMap的兩個(gè)構(gòu)造方法
        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }
        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
}
  • SynchronizedMap 一共有三個(gè)成員變量,序列化ID拋開(kāi)不談,另外兩個(gè)分別是 Map 類型的實(shí)例變量 m,用于接收構(gòu)造方法中傳入的 Map 參數(shù),以及 Object 類型的實(shí)例變量 mutex,作為鎖對(duì)象使用。

  • 再來(lái)看構(gòu)造方法,SynchronizedMap 有兩個(gè)構(gòu)造方法。第一個(gè)構(gòu)造方法需要傳入一個(gè) Map 類型的參數(shù),這個(gè)參數(shù)會(huì)被傳遞給成員變量 m,接下來(lái) SynchronizedMap 所有方法的操作都是針對(duì) m 的操作,需要注意的是這個(gè)參數(shù)不能為空,否則會(huì)由 Objects 類的 requireNonNull() 方法拋出空指針異常,然后當(dāng)前的 SynchronizedMap 對(duì)象 this 會(huì)被傳遞給 mutex 作為鎖對(duì)象;第二個(gè)構(gòu)造方法有兩個(gè)參數(shù),第一個(gè) Map 類型的參數(shù)會(huì)被傳遞給成員變量 m,第二個(gè) Object 類型的參數(shù)會(huì)被傳遞給 mutex 作為鎖對(duì)象。

  • 最后來(lái)看 SynchronizedMap 的主要方法 (只選取了一部分):

    public int size() {
        synchronized (mutex) {return m.size();}
    }
    public boolean isEmpty() {
        synchronized (mutex) {return m.isEmpty();}
    }
    public boolean containsKey(Object key) {
        synchronized (mutex) {return m.containsKey(key);}
    }
    public V get(Object key) {
        synchronized (mutex) {return m.get(key);}
    }
    public V put(K key, V value) {
        synchronized (mutex) {return m.put(key, value);}
    }
    public V remove(Object key) {
        synchronized (mutex) {return m.remove(key);}
    }
    

從源碼可以看出,SynchronizedMap 實(shí)現(xiàn)線程安全的方法也是比較簡(jiǎn)單的,所有方法都是先對(duì)鎖對(duì)象 mutex 上鎖,然后再直接調(diào)用 Map 類型成員變量 m 的相關(guān)方法。這樣一來(lái),線程在執(zhí)行方法時(shí),只有先獲得了 mutex 的鎖才能對(duì) m 進(jìn)行操作。因此,跟 Hashtable 一樣,在同一個(gè)時(shí)間點(diǎn),只能有一個(gè)線程對(duì) SynchronizedMap 對(duì)象進(jìn)行操作,雖然保證了線程安全,卻導(dǎo)致了性能低下。這么看來(lái),連 Hashtable 都被棄用了,那性能同樣低下的 SynchronizedMap 還有什么存在的必要呢?別忘了,后者的構(gòu)造方法需要傳入一個(gè) Map 類型的參數(shù),也就是說(shuō)它可以將非線程安全的 Map 轉(zhuǎn)化為線程安全的 Map,而這正是其存在的意義,以下是 SynchronizedMap 的用法示例 (這里并沒(méi)有演示多線程操作):

Map<String, Integer> map = new HashMap<>();
//非線程安全操作
map.put("one", 1);
Integer one = map.get("one");
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(map);
//線程安全操作
one = synchronizedMap.get("one");
synchronizedMap.put("two", 2);
Integer two = synchronizedMap.get("two");

3. ConcurrentHashMap

接下來(lái)是數(shù)據(jù)結(jié)構(gòu)和線程安全原理都最復(fù)雜的 ConcurrentHashMap。首先必須要感嘆一下,這個(gè)類的結(jié)構(gòu)之復(fù)雜(包含53個(gè)內(nèi)部類),設(shè)計(jì)之精妙(不知道怎么形容,反正就是很精妙),真是令人嘆為觀止。說(shuō)實(shí)話,要想徹底理解 ConcurrentHashMap 的各個(gè)細(xì)節(jié),還是需要相當(dāng)扎實(shí)的基礎(chǔ)并花費(fèi)大量精力的。本文對(duì)于 ConcurrentHashMap 線程安全的原理只是做了宏觀的介紹,想要深入理解的同學(xué),可以去文末的傳送門(mén)。另外,本文著重介紹 JDK 1.8 版本的 ConcurrentHashMap,不過(guò)會(huì)對(duì) JDK 1.7 版本做個(gè)簡(jiǎn)單的回顧。

3.1 JDK 1.7 ConcurrentHashMap鎖實(shí)現(xiàn)原理回顧

Java7 ConcurrentHashMap結(jié)構(gòu)圖

如果你有一定基礎(chǔ)的話,應(yīng)該會(huì)知道分段鎖這個(gè)概念。沒(méi)錯(cuò),這是JDK 1.7版本的 ConcurrentHashMap 實(shí)現(xiàn)線程安全的主要手段,具體一點(diǎn)就是 Segment + HashEntry + ReentrantLock。簡(jiǎn)單來(lái)說(shuō),ConcurrentHashMap 是一個(gè) Segment 數(shù)組(默認(rèn)長(zhǎng)度為16),每個(gè) Segment 又包含了一個(gè) HashEntry 數(shù)組,所以可以看做一個(gè) HashMapSegment 通過(guò)繼承 ReentrantLock 來(lái)進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個(gè) Segment,這樣只要保證每個(gè) Segment 是線程安全的,也就實(shí)現(xiàn)了全局的線程安全。

3.2 JDK 1.8 ConcurrentHashMap 線程安全原理詳解

Java8 ConcurrentHashMap結(jié)構(gòu)圖

JDK 1.8 版本摒棄了之前版本中較為臃腫的 Segment 分段鎖設(shè)計(jì),取而代之的是 Node 數(shù)組 + CAS + synchronized + volatile 的新設(shè)計(jì)。這樣一來(lái),ConcurrentHashMap 不僅數(shù)據(jù)結(jié)構(gòu)變得更簡(jiǎn)單了(與JDK 1.8 的HashMap類似),鎖的粒度也更小了,鎖的單位從 Segment 變成了 Node 數(shù)組中的桶(科普:桶就是指數(shù)組中某個(gè)下標(biāo)位置上的數(shù)據(jù)集合,這里可能是鏈表,也可能是紅黑樹(shù))。說(shuō)到紅黑樹(shù),必須提一下,在JDK 1.8 的 HashMapConcurrentHashMap 中,如果某個(gè)數(shù)組位置上的鏈表長(zhǎng)度過(guò)長(zhǎng)(大于等于8),就會(huì)轉(zhuǎn)化為紅黑樹(shù)以提高查詢效率,不過(guò)這不是本文的重點(diǎn)。以下是 ConcurrentHashMap 線程安全原理的詳細(xì)介紹:

3.2.1 get 操作過(guò)程

? 可以發(fā)現(xiàn)發(fā)現(xiàn)源碼中完全沒(méi)有加鎖的操作,后面會(huì)說(shuō)明原因

  1. 首先計(jì)算hash值,定位到該table索引位置,如果是首節(jié)點(diǎn)符合就返回
  2. 如果遇到擴(kuò)容的時(shí)候,會(huì)調(diào)用標(biāo)志正在擴(kuò)容節(jié)點(diǎn)ForwardingNode的find方法,查找該節(jié)點(diǎn),匹配就返回
  3. 以上都不符合的話,就往下遍歷節(jié)點(diǎn),匹配就返回,否則最后就返回null
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode()); //計(jì)算hash
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {//讀取頭節(jié)點(diǎn)的Node元素
        if ((eh = e.hash) == h) { //如果該節(jié)點(diǎn)就是首節(jié)點(diǎn)就返回
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //hash值為負(fù)值表示正在擴(kuò)容,這個(gè)時(shí)候查的是ForwardingNode的find方法來(lái)定位到nextTable來(lái)
        //eh代表頭節(jié)點(diǎn)的hash值,eh=-1,說(shuō)明該節(jié)點(diǎn)是一個(gè)ForwardingNode,正在遷移,此時(shí)調(diào)用ForwardingNode的find方法去nextTable里找。
        //eh=-2,說(shuō)明該節(jié)點(diǎn)是一個(gè)TreeBin,此時(shí)調(diào)用TreeBin的find方法遍歷紅黑樹(shù),由于紅黑樹(shù)有可能正在旋轉(zhuǎn)變色,所以find里會(huì)有讀寫(xiě)鎖。
        //eh>=0,說(shuō)明該節(jié)點(diǎn)下掛的是一個(gè)鏈表,直接遍歷該鏈表即可。
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        while ((e = e.next) != null) {//既不是首節(jié)點(diǎn)也不是ForwardingNode,那就往下遍歷
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

可能有同學(xué)會(huì)提出疑問(wèn):為什么 get 操作不需要加鎖呢?這個(gè)嘛,也需要看一下源碼:

/**
 * The array of bins. Lazily initialized upon first insertion.
 * Size is always a power of two. Accessed directly by iterators.
 * 這是ConcurrentHashMap的成員變量,用 volatile修飾的Node數(shù)組,保證了數(shù)組在擴(kuò)容時(shí)對(duì)其他線程的可見(jiàn)性
 * 另外需要注意的是,這個(gè)數(shù)組是延遲初始化的,會(huì)在第一次put元素時(shí)進(jìn)行初始化,后面還會(huì)用到這個(gè)知識(shí)點(diǎn)
 */
transient volatile Node<K,V>[] table;

/**
 * 這是ConcurrentHashMap靜態(tài)內(nèi)部類Node的定義,可見(jiàn)其成員變量val和next都使用volatile修飾,可保證
 * 在多線程環(huán)境下線程A修改結(jié)點(diǎn)的val或者新增節(jié)點(diǎn)的時(shí)候是對(duì)線程B可見(jiàn)的
 */
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
}

使用 volatile 關(guān)鍵字已經(jīng)足以保證線程在讀取數(shù)據(jù)時(shí)不會(huì)讀取到臟數(shù)據(jù),所以沒(méi)有加鎖的必要。

3.2.2 put 操作過(guò)程
  1. 第一次 put 元素會(huì)初始化 Node 數(shù)組 (initTable)
  2. put 操作又分為 key (hash 碰撞) 存在時(shí)的插入和 key 不存在時(shí)的插入
  3. put 操作可能會(huì)引發(fā)數(shù)組擴(kuò)容 (tryPresize) 和鏈表轉(zhuǎn)紅黑樹(shù) (treeifyBin)
  4. 擴(kuò)容會(huì)使用到數(shù)據(jù)遷移方法 (transfer)
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    // 得到 hash 值
    int hash = spread(key.hashCode());
    // 用于記錄相應(yīng)鏈表的長(zhǎng)度
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        // 如果數(shù)組"空",進(jìn)行數(shù)組初始化
        if (tab == null || (n = tab.length) == 0)
            // 表的初始化,這里不展開(kāi)了,核心思想是使用sizeCtl的變量和CAS操作進(jìn)行控制,保證數(shù)組在擴(kuò)容時(shí)
            // 不會(huì)創(chuàng)建出多余的表
            tab = initTable();
        // 找該 hash 值對(duì)應(yīng)的數(shù)組下標(biāo),得到第一個(gè)節(jié)點(diǎn) f
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 如果數(shù)組該位置為空,用一次 CAS 操作將這個(gè)新值放入其中即可,這個(gè) put 操作差不多就結(jié)束了
            // 如果 CAS 失敗,那就是有并發(fā)操作,進(jìn)到下一個(gè)循環(huán)就好了(循環(huán)的意思是 CAS 在執(zhí)行失敗后會(huì)進(jìn)行           // 重試)
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        else if ((fh = f.hash) == MOVED)
            // 幫助數(shù)據(jù)遷移,
            tab = helpTransfer(tab, f);
        else { // 到這里就是說(shuō),f 是該位置的頭結(jié)點(diǎn),而且不為空
            V oldVal = null;
            // 獲取數(shù)組該位置的頭結(jié)點(diǎn)鎖對(duì)象
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) { // 頭結(jié)點(diǎn)的 hash 值大于 0,說(shuō)明是鏈表
                        // 用于累加,記錄鏈表的長(zhǎng)度
                        binCount = 1;
                        // 遍歷鏈表
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 如果發(fā)現(xiàn)了"相等"的 key,判斷是否要進(jìn)行值覆蓋,然后也就可以 break 了
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            // 到了鏈表的最末端,將這個(gè)新值放到鏈表的最后面
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key, value, null);
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) { // 紅黑樹(shù)
                        Node<K,V> p;
                        binCount = 2;
                        // 調(diào)用紅黑樹(shù)的插值方法插入新節(jié)點(diǎn)
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            if (binCount != 0) {
                // 判斷是否要將鏈表轉(zhuǎn)換為紅黑樹(shù),臨界值和 HashMap 一樣,也是 8
                if (binCount >= TREEIFY_THRESHOLD)
                    // 這個(gè)方法和 HashMap 中稍微有一點(diǎn)點(diǎn)不同,那就是它不是一定會(huì)進(jìn)行紅黑樹(shù)轉(zhuǎn)換,
                    // 如果當(dāng)前數(shù)組的長(zhǎng)度小于 64,那么會(huì)選擇進(jìn)行數(shù)組擴(kuò)容,而不是轉(zhuǎn)換為紅黑樹(shù)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    addCount(1L, binCount);
    return null;
}

以上是 put 方法的源碼和分析,其中涉及到的其他方法,比如 initTable,helpTransfertreeifyBintryPresize 等方法不再一一展開(kāi),有興趣的同學(xué)可以去文末傳送門(mén)看詳細(xì)解析。

3.2.3 CAS 操作簡(jiǎn)要介紹

? CAS 操作是新版本 ConcurrentHashMap 線程安全實(shí)現(xiàn)原理的精華所在,如果說(shuō)其共享變量的讀取全靠 volatile 實(shí)現(xiàn)線程安全的話,那么存儲(chǔ)和修改過(guò)程除了使用少量的 synchronized 關(guān)鍵字外,主要是靠 CAS 操作實(shí)現(xiàn)線程安全的。不了解 CAS 操作的同學(xué)看這里 JAVA CAS原理深度分析

// CAS操作的提供者
private static final sun.misc.Unsafe U;

// 以下是put方法里用到CAS操作的代碼片段
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    if (casTabAt(tab, i, null,
                 new Node<K,V>(hash, key, value, null)))
        break;
}

// tabAt方法通過(guò)Unsafe.getObjectVolatile()的方式獲取數(shù)組對(duì)應(yīng)index上的元素,getObjectVolatile作用于對(duì)
// 應(yīng)的內(nèi)存偏移量上,是具備volatile內(nèi)存語(yǔ)義的。
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);
}
// 如果獲取的是空,嘗試用CAS的方式在數(shù)組的指定index上創(chuàng)建一個(gè)新的Node。
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);
}

ConcurrentHashMap 中,數(shù)組初始化、插入刪除元素、擴(kuò)容、數(shù)據(jù)遷移以及鏈表和紅黑樹(shù)的轉(zhuǎn)換等過(guò)程都會(huì)涉及到線程安全問(wèn)題,而相關(guān)的方法中實(shí)現(xiàn)線程安全的思想是一致的:對(duì)桶中的數(shù)據(jù)進(jìn)行添加或修改操作時(shí)會(huì)用到 synchronized 關(guān)鍵字,也就是獲得該位置上頭節(jié)點(diǎn)對(duì)象的鎖,保證線程安全,另外就是用到了大量的 CAS 操作。以上就是對(duì)這三種 Map 的線程安全原理的簡(jiǎn)要介紹。

二、性能測(cè)試

直接上代碼

public class MapPerformanceTest {
    private static final int THREAD_POOL_SIZE = 5;
    private static Map<String, Integer> hashtableObject = null;
    private static Map<String, Integer> concurrentHashMapObject = null;
    private static Map<String, Integer> synchronizedMap = null;

    private static void performanceTest(final Map<String, Integer> map) throws InterruptedException {
        System.out.println(map.getClass().getSimpleName() + "性能測(cè)試開(kāi)始了... ...");
        long totalTime = 0;
        // 進(jìn)行五次性能測(cè)試,每次開(kāi)啟五個(gè)線程,每個(gè)線程對(duì) map 進(jìn)行500000次查詢操作和500000次插入操作
        for (int i = 0; i < 5; i++) {
            long startTime = System.nanoTime();
            ExecutorService service = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
            for (int j = 0; j < THREAD_POOL_SIZE; j++) {
                service.execute(() -> {
                    for (int k = 0; k < 500000; k++) {
                        Integer randomNumber = (int)Math.ceil(Math.random() * 500000);
                        // 從map中查找數(shù)據(jù),查找結(jié)果并不會(huì)用到,這里不能用int接收返回值,因?yàn)镮nteger可能是
                        // null,賦值給int會(huì)引發(fā)空指針異常
                        Integer value = map.get(String.valueOf(randomNumber));
                        //向map中添加元素
                        map.put(String.valueOf(randomNumber), randomNumber);
                    }
                });
            }
            //關(guān)閉線程池
            service.shutdown();
            service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);

            long endTime = System.nanoTime();
            // 單次執(zhí)行時(shí)間
            long singleTime = (endTime - startTime) / 1000000;
            System.out.println("第" + (i + 1) + "次測(cè)試耗時(shí)" + singleTime + "ms...");
            totalTime += singleTime;
        }
        System.out.println("平均耗時(shí)" + totalTime / 5 + "ms...");
    }

    public static void main(String[] args) throws InterruptedException {
        //Hashtable性能測(cè)試
        hashtableObject = new Hashtable<>();
        performanceTest(hashtableObject);

        //SynchronizedMap性能測(cè)試
        Map<String, Integer> map = new HashMap<>(500000);
        synchronizedMap = Collections.synchronizedMap(map);
        performanceTest(synchronizedMap);

        //ConcurrentHashMap性能測(cè)試
        concurrentHashMapObject = new ConcurrentHashMap<>(5000000);
        performanceTest(concurrentHashMapObject);
    }
}

在這里說(shuō)明一點(diǎn),這段代碼在不同環(huán)境下運(yùn)行的結(jié)果會(huì)存在差別,但是結(jié)果的數(shù)量級(jí)對(duì)比應(yīng)該是一致的,以下是我機(jī)子上的運(yùn)行結(jié)果:


Map性能測(cè)試結(jié)果

從運(yùn)行結(jié)果可以看出,在250萬(wàn)這個(gè)數(shù)量級(jí)別的數(shù)據(jù)存取上,HashtableSynchronizedMap 的性能表現(xiàn)幾乎一致,畢竟它們的鎖實(shí)現(xiàn)原理大同小異,而 ConcurrentHashMap 表現(xiàn)出了比較大的性能優(yōu)勢(shì),耗時(shí)只有前兩者的三分之一多一點(diǎn)兒。嗯... 以后面試再被問(wèn)到相關(guān)問(wèn)題,可以直接把數(shù)據(jù)甩給面試官... ...

三、結(jié)語(yǔ)

好了,以上就是本篇文章的全部?jī)?nèi)容了,完結(jié)撒花,簡(jiǎn)書(shū)上不知道怎么調(diào)圖片大小哈哈。第一次寫(xiě)博客,竟然嘮叨了這么多,不足之處還請(qǐng)各位看官老爺不吝賜教。另外想要進(jìn)一步深入了解 ConcurrentHashMap 原理的朋友可以看一下下面兩篇文章,是我看過(guò)的講的比較詳細(xì)的。

解讀Java8中ConcurrentHashMap是如何保證線程安全的

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

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

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