[英]Java: Caching of non-volatile variables by different threads
情況如下:
問題:我是否必須使此 class 的所有變量都易失?
關注點:
注意:我知道構建器模式,但由於其他幾個原因,我不能在那里應用它:(
已編輯:由於我覺得 Mathias 和 axtavt 的兩個答案不太匹配,所以我想添加一個示例:
假設我們有一個foo
class:
class Foo {
public int x=0;
}
並且兩個線程正在使用它,如上所述:
// Thread 1 init the value:
Foo f = new Foo();
f.x = 5;
values.add(f); // Publication via thread-safe collection like Vector or Collections.synchronizedList(new ArrayList(...)) or ConcurrentHashMap?.
// Thread 2
if (values.size()>0){
System.out.println(values.get(0).x); // always 5 ?
}
據我了解Mathias,它可以根據JLS在一些JVM上打印出0。 據我了解 axtavt 它總是會打印 5。
你有什么意見?
——問候,德米特里
在這種情況下,在使 object 可用於其他線程時,您需要使用安全發布習慣用法,即(來自Java Concurrency in Practice ):
- 從 static 初始化程序初始化 object 引用;
- 將對它的引用存儲到 volatile 字段或 AtomicReference 中;
- 將對其的引用存儲到正確構造的 object 的最終字段中; 或者
- 將對它的引用存儲到由鎖正確保護的字段中。
如果您使用安全發布,則不需要聲明字段volatile
。
但是,如果您不使用它,則聲明字段volatile
(理論上)將無濟於事,因為volatile
引起的 memory 障礙是一方面:volatile write 可以在其后使用非易失性操作重新排序。
因此, volatile
在以下情況下確保正確性:
class Foo {
public int x;
}
volatile Foo foo;
// Thread 1
Foo f = new Foo();
f.x = 42;
foo = f; // Safe publication via volatile reference
// Thread 2
if (foo != null)
System.out.println(foo.x); // Guaranteed to see 42
但在這種情況下不起作用:
class Foo {
public volatile int x;
}
Foo foo;
// Thread 1
Foo f = new Foo();
// Volatile doesn't prevent reordering of the following actions!!!
f.x = 42;
foo = f;
// Thread 2
if (foo != null)
System.out.println(foo.x); // NOT guaranteed to see 42,
// since f.x = 42 can happen after foo = f
從理論的角度來看,在第一個樣本中存在一個可傳遞的發生前關系
f.x = 42 happens before foo = f happens before read of foo.x
在第二個示例中, fx = 42
和foo.x
的讀取沒有通過happens-before關系鏈接,因此它們可以按任何順序執行。
在讀取該字段的線程上調用start
方法之前,您無需聲明字段 volatile 的值已設置。
原因是在這種情況下,設置與另一個線程中的讀取處於先發生關系(如 Java 語言規范中所定義)。
JLS的相關規則是:
但是,如果在設置字段之前啟動其他線程,則必須將字段聲明為 volatile。 JLS 不允許您假設線程在第一次讀取值之前不會緩存該值,即使在 JVM 的特定版本上可能是這種情況。
為了完全了解發生了什么,我一直在閱讀有關 Java Memory Model (JMM) 的信息。 可以在 Java 實踐中的並發中找到對 JMM 的有用介紹。
我認為問題的答案是:是的,在給出的示例中,使 object 的成員易失是不必要的。 但是,這種實現相當脆弱,因為這種保證取決於完成事情的確切順序以及容器的線程安全性。 構建器模式將是一個更好的選擇。
為什么保證:
線程安全容器的 add 方法必須使用一些同步構造,如 volatile 讀/寫、lock 或 synchronized()。 這保證了兩件事:
對象在發布后永遠不會被修改。
但是,如果容器不是線程安全的,或者事物的順序被不知道該模式的人更改了,或者對象在發布后被意外更改,則不再有任何保證。 因此,遵循 Builder 模式,可以由 google AutoValue 或 Freebuilder 生成,這樣會更安全。
這篇文章的主題也相當不錯: http://tutorials.jenkov.com/java-concurrency/volatile.html
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.