[英]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#val
是volatile
,它在訂購之前確定您的發生。
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#value
是volatile
。 這是線程安全的同步點。
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之前的操作的關系之前。
該文檔未聲明同步發生。 例如它說明
[...]匯總操作(如
putAll
和clear
,並發檢索)可能反映僅插入或刪除某些條目。
換句話說,允許並發使用和提供同步訪問之間存在差異。
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.