volatile 发布保证有多深?
How deep volatile publication guarantees?
众所周知,如果我们有一些对象引用并且这个引用有 final 字段 - 我们将从 final 字段看到所有可到达的字段(至少在构造函数完成时)
示例 1:
class Foo{
private final Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
据我了解,在这种情况下,我们可以保证 bar()
方法始终输出 object
,因为:
1. 我列出了 class Foo
的完整代码并且地图是最终的;
2。如果某些线程将看到 Foo
的引用并且此引用 != null,那么我们可以保证从最终 map
引用值可访问的值将是实际的 .
我也觉得
示例 2:
class Foo {
private final Map map;
private Map nonFinalMap;
Foo() {
nonFinalMap = new HashMap();
nonFinalMap.put(2, "ololo");
map = new HashMap();
map.put(1, "object");
}
public void bar() {
System.out.println(map.get(1));
}
public void bar2() {
System.out.println(nonFinalMap.get(2));
}
}
这里我们对 bar()
方法有相同的保证,但是 bar2
可以抛出 NullPointerException
尽管 nonFinalMap
赋值发生在 map
赋值之前。
我想知道 volatile 怎么样:
示例 3:
class Foo{
private volatile Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
据我所知,bar()
方法不能抛出 NullPoinerException
但它可以打印 null
; (这方面我完全不确定)
示例 4:
class Foo {
private volatile Map map;
private Map nonVolatileMap;
Foo() {
nonVolatileMap= new HashMap();
nonVolatileMap.put(2, "ololo");
map = new HashMap();
map.put(1, "object");
}
public void bar() {
System.out.println(map.get(1));
}
public void bar2() {
System.out.println(nonFinalMap.get(2));
}
}
我认为这里我们对 bar()
方法有相同的保证 bar2()
不能抛出 NullPointerException
因为 nonVolatileMap
赋值写入更高的 volatile map 赋值但它可以输出 null
在 Elliott Frisch 评论后添加
通过比赛示例发布:
public class Main {
private static Foo foo;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
foo = new Foo();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (foo == null) ; // empty loop
foo.bar();
}
}).start();
}
}
请证明或更正我对代码片段的评论。
在当前Java内存模型领域,volatile
不等于final
。也就是说,you cannot replace final
with volatile
,和认为安全施工保障是一样的。值得注意的是,这在理论上是可以发生的:
public class M {
volatile int x;
M(int v) { this.x = v; }
int x() { return x; }
}
// thread 1
m = new M(42);
// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"
因此,在构造函数中写入 volatile
字段并不安全。
直觉:在上面的例子中m
有一场比赛。通过使 field M.x
volatile
不会消除该种族,只有使 m
本身 volatile
才会有所帮助。换句话说,该示例中的 volatile
修饰符在 错误的位置 是有用的。在安全发布中,您必须有 "writes -> volatile write -> volatile read that observes volatile write -> reads (now observing writes prior the volatile write)",而不是 "volatile write -> write -> read -> volatile read (that does not observe the volatile write)".
知识点 1: 这 属性 意味着我们可以在构造函数中更积极地优化 volatile
-s。这证实了可以放松未观察到的易失性存储的直觉(实际上直到具有 non-escaping this
的构造函数完成后才观察到)。
知识点2: 这也意味着你不能安全地初始化volatile
变量。将上面示例中的 M
替换为 AtomicInteger
,您就会有一个奇怪的 real-life 行为!在一个线程中调用 new AtomicInteger(42)
,不安全地发布实例,并在另一个线程中执行 get()
——你保证观察到 42
吗?如前所述,JMM 表示 "nope"。 Java 内存模型的较新版本努力保证所有初始化的安全构造,以捕获这种情况。还有许多非 x86 端口 have already strengthened this 是安全的。
知识问答 3: Doug Lea:"This final
vs volatile
issue has led to some twisty constructions in java.util.concurrent to allow 0 as the base/default value in cases where it would not naturally be. This rule sucks and should be changed."
也就是说,这个例子可以做得更巧妙:
public class C {
int v;
C(int v) { this.x = v; }
int x() { return x; }
}
public class M {
volatile C c;
M(int v) { this.c = new C(v); }
int x() {
while (c == null); // wait!
return c.x();
}
}
// thread 1
m = new M(42);
// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"
如果通过 volatile
字段 after volatile read 观察到在构造函数中由 volatile write 写入的值,通常的安全发布规则就会生效。
众所周知,如果我们有一些对象引用并且这个引用有 final 字段 - 我们将从 final 字段看到所有可到达的字段(至少在构造函数完成时)
示例 1:
class Foo{
private final Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
据我了解,在这种情况下,我们可以保证 bar()
方法始终输出 object
,因为:
1. 我列出了 class Foo
的完整代码并且地图是最终的;
2。如果某些线程将看到 Foo
的引用并且此引用 != null,那么我们可以保证从最终 map
引用值可访问的值将是实际的 .
我也觉得
示例 2:
class Foo {
private final Map map;
private Map nonFinalMap;
Foo() {
nonFinalMap = new HashMap();
nonFinalMap.put(2, "ololo");
map = new HashMap();
map.put(1, "object");
}
public void bar() {
System.out.println(map.get(1));
}
public void bar2() {
System.out.println(nonFinalMap.get(2));
}
}
这里我们对 bar()
方法有相同的保证,但是 bar2
可以抛出 NullPointerException
尽管 nonFinalMap
赋值发生在 map
赋值之前。
我想知道 volatile 怎么样:
示例 3:
class Foo{
private volatile Map map;
Foo(){
map = new HashMap();
map.put(1,"object");
}
public void bar(){
System.out.println(map.get(1));
}
}
据我所知,bar()
方法不能抛出 NullPoinerException
但它可以打印 null
; (这方面我完全不确定)
示例 4:
class Foo {
private volatile Map map;
private Map nonVolatileMap;
Foo() {
nonVolatileMap= new HashMap();
nonVolatileMap.put(2, "ololo");
map = new HashMap();
map.put(1, "object");
}
public void bar() {
System.out.println(map.get(1));
}
public void bar2() {
System.out.println(nonFinalMap.get(2));
}
}
我认为这里我们对 bar()
方法有相同的保证 bar2()
不能抛出 NullPointerException
因为 nonVolatileMap
赋值写入更高的 volatile map 赋值但它可以输出 null
在 Elliott Frisch 评论后添加
通过比赛示例发布:
public class Main {
private static Foo foo;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
foo = new Foo();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (foo == null) ; // empty loop
foo.bar();
}
}).start();
}
}
请证明或更正我对代码片段的评论。
在当前Java内存模型领域,volatile
不等于final
。也就是说,you cannot replace final
with volatile
,和认为安全施工保障是一样的。值得注意的是,这在理论上是可以发生的:
public class M {
volatile int x;
M(int v) { this.x = v; }
int x() { return x; }
}
// thread 1
m = new M(42);
// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // allowed to print "0"
因此,在构造函数中写入 volatile
字段并不安全。
直觉:在上面的例子中m
有一场比赛。通过使 field M.x
volatile
不会消除该种族,只有使 m
本身 volatile
才会有所帮助。换句话说,该示例中的 volatile
修饰符在 错误的位置 是有用的。在安全发布中,您必须有 "writes -> volatile write -> volatile read that observes volatile write -> reads (now observing writes prior the volatile write)",而不是 "volatile write -> write -> read -> volatile read (that does not observe the volatile write)".
知识点 1: 这 属性 意味着我们可以在构造函数中更积极地优化 volatile
-s。这证实了可以放松未观察到的易失性存储的直觉(实际上直到具有 non-escaping this
的构造函数完成后才观察到)。
知识点2: 这也意味着你不能安全地初始化volatile
变量。将上面示例中的 M
替换为 AtomicInteger
,您就会有一个奇怪的 real-life 行为!在一个线程中调用 new AtomicInteger(42)
,不安全地发布实例,并在另一个线程中执行 get()
——你保证观察到 42
吗?如前所述,JMM 表示 "nope"。 Java 内存模型的较新版本努力保证所有初始化的安全构造,以捕获这种情况。还有许多非 x86 端口 have already strengthened this 是安全的。
知识问答 3: Doug Lea:"This final
vs volatile
issue has led to some twisty constructions in java.util.concurrent to allow 0 as the base/default value in cases where it would not naturally be. This rule sucks and should be changed."
也就是说,这个例子可以做得更巧妙:
public class C {
int v;
C(int v) { this.x = v; }
int x() { return x; }
}
public class M {
volatile C c;
M(int v) { this.c = new C(v); }
int x() {
while (c == null); // wait!
return c.x();
}
}
// thread 1
m = new M(42);
// thread 2
M lm;
while ((lm = m) == null); // wait for it
print(lm.x()); // always prints "42"
如果通过 volatile
字段 after volatile read 观察到在构造函数中由 volatile write 写入的值,通常的安全发布规则就会生效。