我們正在嘗試為特定鍵存盤唯一物件。在多執行緒環境中呼叫 getMyObject 時,我們在 return 陳述句時得到 null ptr 例外
object SampleClass
{
fun getMyObject(Id : String) : MyObject
{
if(!myMap.containsKey(Id))
{
synchronized(SampleClass)
{
if(!myMap.containsKey(Id))
{
myMap[Id] = MyObject()
}
}
}
return myMap[Id]!!
}
private val myMap = HashMap<String,MyObject>()
}
似乎即使 contains 方法在我們嘗試獲取值時回傳 true,但該值回傳 null。我不確定它背后的原因是什么。
uj5u.com熱心網友回復:
如果您試圖超越記憶體模型,就會遇到這種麻煩。如果您查看HashMap的源代碼,您會發現containsKey實作為:
public boolean containsKey(Object key) {
return getNode(key) != null;
}
請注意,僅當存在HashMap.Node與給定鍵對應的物件時,它才會回傳 true。現在,這是如何get實作的:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(key)) == null ? null : e.value;
}
您所看到的是不安全發布問題的一個實體。假設 2 個執行緒(A 和 B)呼叫getMyObject一個不存在的鍵。synchronizedA 稍微領先于 B,因此它在 B 呼叫 之前進入了塊containsKey。特別是, Aput在 B 呼叫之前呼叫containsKey。呼叫put創建一個新Node物件并將其放入哈希映射的內部資料結構中。
現在,考慮 BcontainsKey在 A 存在synchronized塊之前呼叫的情況。B可能會看到NodeA 放置的物件,在這種情況下containsKey回傳 true。然而,此時節點被不安全地發布,因為它被 B 以非同步方式并發訪問。不能保證它的建構式(設定其value欄位的那個)已被呼叫。即使它被呼叫,也不能保證value參考(或建構式設定的任何參考)與節點參考一起發布。這意味著 B 可以看到一個不完整的節點:節點參考但看不到它的值或它的任何欄位。當 B 前進到 時get,它讀取null為不安全發布節點的值。因此NullPointerException.
這是一個用于可視化的臨時圖表:
Thread A Thread B
- Enter the synchronized block
- Call hashMap.put(...)
- Insert a new Node
- See the newly inserted (but not yet
initialized from the perspective of B)
Node in HashMap.containsKey
- Return node.value (still null)
from HashMap.get
- !! throws a `NullPointerException`
...
- Exit the synchronized block
(now the node is safely published)
以上只是可能出錯的一種情況(見評論)。為避免此類危險,請使用ConcurrentHashMap(例如map.computeIfAbsent(key, key -> new MyObject()))或永遠不要在塊HashMap之外同時訪問您的。synchronized
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/496643.html
