首先來看string
這個類的hashcode
方法,如下
public
inthashcode()
hash = h;
/* **② */
}return
(h);
/* **③ */
}
hash
是string
類的乙個屬性,可以看到這邊首先是**①讀取了本地屬性的值,並且賦值給區域性變數h
。並且使用h
進行了業務邏輯的判斷。如果h
沒有值的話,就進行 hash 值的生成,並且賦值到h
上,並且在**②處賦值給了屬性hash
。最終返回的,也是區域性變數h
的值。那麼上述的**能否修改為下面的模式
public
inthashcode()
hash = h;
}return
(hash)
;/* **② */
}
修改的**沒有區域性變數,直接使用屬性本身來操作。
答案是否定的,因為這種寫法是執行緒不安全的,可能導致方法的返回值是 0 。似乎有點費解,因為如果hash
值為0 ,則**會進入迴圈體,對hash
值進行更新。所以乍看之下,無論如何是不會返回 0 的。
上述的理解邏輯,在單執行緒環境下,是正確的。但是這段**工作在多執行緒環境。實際上,上述**有兩次對hash
值的讀取,分別是**①和②。可能會出現一種情況,在**①處,讀取到hash
值不為 0 ,在**②處,讀取到hash
值為0,並且以此為結果返回了。顯然此時這種結果是錯誤的。
要理解這種場景的發生需要從 jmm 的規則談起。首先,兩個讀取之間是沒有因果關係的,因此不存在第乙個對變數的讀取觀察到了值,第二個對該變數的讀取也要觀察到這個值。其次,在 jmm 中,對乙個變數的讀取操作允許其觀察最後一次到對該變數的寫入,只要沒有 hb 關係來阻止這個讀取的觀察效果。此外,物件屬性的預設值也是由寫入動作觸發的。這意味著對hash
值的寫入有兩個地方,乙個在於物件構造時,乙個在於其他執行緒對hash
值的寫入。由於這兩個寫入沒有 hb 關係,因此對hash
的讀取可能讀取到任意乙個寫入的結果。所以,可能會出現的情況是在**①處讀取到了其他執行緒對hash
值的寫入,因此跳過了內部的寫入邏輯。而在**②處再次讀取hash
值,此時讀取到了物件構造時對hash
預設值的寫入,導致返回 0 。
從 jmm 規則角度是最正確的理解,但是為了形象的想象這一切如何發生,我們可以將上面的程式修改如下
public
inthashcode()
a = hash = h;
}return
(a);
/* **② */
}
實際上,這的確是在執行**邏輯的時候,一種可能的**重排序變種。假定一開始hash
值為0,則a
為 0 。在if
判斷的時候,hash
讀取到了其他執行緒寫入的值,因此沒有執行計算邏輯,最終返回了a
的值,也就是 0 。 DLL 執行緒區域性變數
1.用 declspec thread 建立執行緒區域性變數 declspec thread int tls count 0 注意事項 當用 declspec thread 宣告執行緒區域性變數的時候,應注意以下事項 1 只能用來宣告或者定義具有static作用域的變數,而不能用來宣告或者定義區域性...
關於區域性變數就是執行緒安全的理解
網上看到好多定論說是區域性變數就是執行緒安全的。但是我覺得對大家都造成了一些誤解,例如 以下 package test public class demo 5 售票視窗類 class ticketwindow implements runnable if me catch interruptedex...
為什麼區域性變數是執行緒安全的
例如,有三個方法 a b c,他們的呼叫關係是 a b c a 呼叫 b,b 呼叫 c 在執行時,會構建出下面這樣的呼叫棧。每個方法在呼叫棧裡都有自己的獨立空間,稱為棧幀,每個棧幀裡都有對應方法需要的引數和返回位址。當呼叫方法時,會建立新的棧幀,並壓入呼叫棧 當方法返回時,對應的棧幀就會被自動彈出。...