簡體   English   中英

Java 8 ConcurrentHashMap

[英]Java 8 ConcurrentHashMap

我發現ConcurrentHashMap已經在Java 8中被完全重寫為更“無鎖”。 我瀏覽了get()方法的代碼,看到沒有明確的鎖機制:

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    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;
        }
        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;
}

題:

如何從一個線程中看到從其他線程對此hashmap進行的修改,因為代碼不在同步傘下(這會強制執行先發生關系)?

注意:整個ConcurrentHashMap是表的包裝器: transient volatile Node<K,V>[] table;

所以table是對數組的易變引用,而不是對volatile元素數組的引用! 這意味着如果有人正在更新此數組中的元素,則在其他線程中將看不到修改。

簡短的回答

Node#valvolatile ,它在訂購之前確定您的發生。

更長的答案

synchronized不是線程安全的必要條件,它是工具箱中的一個工具,可以使系統線程安全。 您將不得不考慮此ConcurrentHashMap上的一整套操作來推斷線程安全性。

知道原始的ConcurrentHashMap也是非阻塞的,這很有用。 注意Java-CHM之前的版本

V get(Object key, int hash) {
    if (count != 0) { // read-volatile
        HashEntry<K,V> e = getFirst(hash);
        while (e != null) {
            if (e.hash == hash && key.equals(e.key)) {
                V v = e.value;
                if (v != null)
                    return v;
                return readValueUnderLock(e); // ignore this
            }
            e = e.next;
        }
    }
    return null;
}

在這種情況下,沒有阻塞,那么它是如何工作的? HashEntry#valuevolatile 這是線程安全的同步點。

CHM-8的Node類是相同的。

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    volatile V val;
    volatile Node<K,V> next;

因此,在這種情況下,非null值應該確保在發生之前發生 - 在關於put之前的操作的關系之前。

該文檔未聲明同步發生。 例如它說明

[...]匯總操作(如putAllclear ,並發檢索)可能反映僅插入或刪除某些條目。

換句話說,允許並發使用和提供同步訪問之間存在差異。

Java語言規范寫道

如果我們有兩個動作x和y,我們寫hb(x,y)來表示x發生在y之前。

  • 如果x和y是同一個線程的動作,並且x在程序順序中出現在y之前,那么hb(x,y)。

  • 從對象的構造函數的末尾到該對象的終結器(第12.6節)的開始有一個發生前的邊緣。

  • 如果動作x與后續動作y同步,那么我們也有hb(x,y)。

  • 如果是hb(x,y)和hb(y,z),那么hb(x,z)。

定義

同步動作引發與動作的同步關系,定義如下:

  • 監視器m上的解鎖動作與m上的所有后續鎖定動作同步(其中“后續”根據同步順序定義)。

  • 對易失性變量v(第8.3.1.4節)的寫入與任何線程對v的所有后續讀取同步(其中“后續”根據同步順序定義)。

  • 啟動線程的操作與其啟動的線程中的第一個操作同步。

  • 向每個變量寫入默認值(零,false或null)與每個線程中的第一個操作同步。

    雖然在分配包含變量的對象之前向變量寫入默認值似乎有點奇怪,但從概念上講,每個對象都是在程序開始時使用其默認初始化值創建的。

  • 線程T1中的最終操作與另一個檢測到T1已終止的線程T2中的任何操作同步。

    T2可以通過調用T1.isAlive()或T1.join()來完成此操作。

  • 如果線程T1中斷線程T2,則T1的中斷與任何其他線程(包括T2)確定T2已被中斷的任何點同步(通過拋出InterruptedException或通過調用Thread.interrupted或Thread.isInterrupted)。

也就是說,讀取易失性字段確定發生 - 就像顯式鎖定一樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM