[英]Making this visibility example fail
在Java Concurrency in Practice中我認為令人驚訝的一個例子(至少我)是這樣的:
public class Foo {
private int n;
public Foo(int n) {
this.n = n;
}
public void check() {
if (n != n) throw new AssertionError("huh?");
}
}
令我驚訝的是(至少對我來說)聲稱這不是線程安全的,不僅它不安全,而且檢查方法也有可能拋出斷言錯誤。
解釋是沒有同步/標記n作為volatile,在不同的線程之間沒有可見性保證,並且n的值可以在線程讀取時改變。
但我想知道它在實踐中發生的可能性有多大。 或者更好,如果我能以某種方式復制它。
所以我試圖編寫會觸發斷言錯誤的代碼,沒有運氣。
有沒有直接的方法來編寫一個測試,證明這個可見性問題不僅僅是理論上的?
或者它是否在最近的JVM中發生了變化?
編輯 :相關問題: 非線程安全對象發布
但我想知道它在實踐中發生的可能性有多大。
非常不可能的esp,因為JIT可以將n
轉換為局部變量並且只讀取一個。
極不可能的線程安全漏洞的問題在於,有一天,您可能會更改一些無關緊要的內容,例如您選擇的處理器或JVM,並且突然您的代碼會隨機中斷。
或者更好,如果我能以某種方式復制它。
不保證您也可以重現它。
有沒有直接的方法來編寫一個測試,證明這個可見性問題不僅僅是理論上的?
在某些情況下,是的。 但是這個是難以證明的,部分原因是因為JVM沒有被阻止比JLS說的最小的線程更安全。
例如,HotSpot JVM通常會執行您期望的操作,而不僅僅是文檔中的最小值。 例如,根據javadoc,System.gc()只是一個提示,但默認情況下,HotSpot JVM每次都會這樣做。
這種情況非常不可能,但仍有可能。 在加載比較中的第一個n
之前,線程必須暫停,在第二個比較之前並且它們被比較 - 這需要非常小的一小部分,你必須非常幸運地擊中它。 但是如果你把這個代碼寫在一個超級關鍵的應用程序中,數百萬用戶每天都會在全世界范圍內使用它,它遲早會發生 - 這只是一個時間問題。
無法保證您可以重現它 - 甚至可能在您的機器上無法重現。 這取決於您的平台,VM,Java編譯器等...
您可以將前n
轉換為局部變量,然后暫停線程(Sleep),並在進行比較之前讓第二個線程更改n。 但我認為這種情況會破壞證明你案件的目的。
如果Foo
發布不安全,理論上另一個線程在讀取n
兩次時會觀察到兩個不同的值。
由於這個原因,以下程序可能會失敗。
public static Foo shared;
public static void main(String[] args)
{
new Thread(){
@Override
public void run()
{
while(true)
{
Foo foo = shared;
if(foo!=null)
foo.check();
}
}
}.start();
new Thread(){
@Override
public void run()
{
while(true)
{
shared = new Foo(1); // unsafe publication
}
}
}.start();
}
然而,幾乎不可能觀察到它失敗了; VM可能會將n!=n
優化為false
而不實際讀取n
兩次。
但是就Java內存模型而言,我們可以展示一個等效的程序,即對前一個程序的有效轉換,並觀察它是否立即失敗
static public class Foo
{
int n;
public Foo()
{
}
public void check()
{
int n1 = n;
no_op();
int n2 = n;
if (n1 != n2)
throw new AssertionError("huh?");
}
}
// calling this method has no effect on memory semantics
static void no_op()
{
if(Math.sin(1)>1) System.out.println("never");
}
public static Foo shared;
public static void main(String[] args)
{
new Thread(){
@Override
public void run()
{
while(true)
{
Foo foo = shared;
if(foo!=null)
foo.check();
}
}
}.start();
new Thread(){
@Override
public void run()
{
while(true)
{
// a valid transformation of `shared=new Foo(1)`
Foo foo = new Foo();
shared = foo;
no_op();
foo.n = 1;
}
}
}.start();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.