易失性保证和无序执行

重要的编辑我知道在发生两个任务的线程 “发生之前”我的问题是有可能是另一个线程读取“b”非空,而“a”仍然为空。 所以我知道,如果您从之前调用setBothNonNull(…)的线程调用doIt() ,那么它不能抛出NullPointerException exception 。 但是如果从另一个线程调用doIt() 不是调用setBothNonNull(…)

请注意,这个问题是关于volatile关键字volatile保证:它不是关于synchronized关键字(所以请不要回答“你必须使用同步”,因为我没有任何问题来解决:我只是想了解有关无序执行的volatile担保(或缺乏担保)。

假设我们有一个包含两个volatilestring引用的对象,这些引用被构造函数初始化为null,并且我们只有一种方法来修改这两个string:通过调用setBoth(…)并且我们只能将它们的引用设置为非-null引用(只允许构造函数将它们设置为null)。

例如(这只是一个例子,现在还没有问题):

 public class SO { private volatile String a; private volatile String b; public SO() { a = null; b = null; } public void setBothNonNull( @NotNull final String one, @NotNull final String two ) { a = one; b = two; } public String getA() { return a; } public String getB() { return b; } } 

setBothNoNull(…)中 ,分配非空参数“a”的行出现在分配非空参数“b”的行之前。

那么如果我这样做(再一次,这是毫无疑问的,接下来就是问题了):

 doIt() { if ( so.getB() != null ) { System.out.println( so.getA().length ); } } 

我的理解是正确的,由于乱序执行,我可以得到一个NullPointerException

换句话说:不能保证,因为我读了一个非空的“b”,我会读一个非空的“a”?

因为由于乱序(多)处理器和volatile作品“b”可以在“a”之前分配的方式?

volatile保证,写入后的读取总是能看到最后的写入值,但这里有一个无序的“问题”吗? (再一次,“问题”是为了试图理解volatile关键字和Java存储器模型的语义,而不是解决问题)。

不,你永远不会得到一个NPE。 这是因为volatile也具有引入“先发生”关系的记忆效应。 换句话说,它将防止重新sorting

 a = one; b = two; 

上面的语句不会被重新sorting,如果b已经有值two ,所有的线程都会观察值one

大卫·霍姆斯(David Holmes)在这里解释道:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results

编辑(响应后续):福尔摩斯说的是,如果只有线程A,编译器可以在理论上做一个重新sorting。但是,还有其他线程,他们可以检测重新sorting。 这就是为什么不允许编译器重新sorting的原因。 Java内存模型需要编译器专门确保没有线程会检测到这种重新sorting。

但是如果从另一个线程调用doIt()而不是调用setBothNonNull(…)?

不,你将永远不会有一个NPE。 volatile语义确实强加了线程间的sorting。 这意味着,对于所有现有的线程,分配one之前发生的two分配。

我的理解是正确的,由于乱序执行,我可以得到一个NullPointerException? 换句话说:不能保证,因为我读了一个非空的“b”,我会读一个非空的“a”?

假设分配给ab或非null的值,我认为你的理解是不正确的 。 JLS说:

1 )如果x和y是同一个线程的动作,并且x以程序顺序在y之前,则hb(x,y)。

2 )如果动作x与下面的动作y同步,那么我们也有hb(x,y)。

3 )如果hb(x,y)和hb(y,z),则hb(x,z)。

4 )对volatilevariables(§8.3.1.4)的写入v 与任何线程 (其中随后根据同步顺序定义)的所有后续v读取进行同步。

定理

鉴于线程#1已经调用setBoth(...); 一次,并且参数非空,并且线程#2已经观察到b是非空的,则线程#2不能观察到a为空。

非正式的证据

  1. 通过( 1 )-hb(write(a,non-null),write(b,non-null))在线程#1
  2. 由( 2 )和( 4 )-hb(write(b,non-null),read(b,non-null))
  3. 通过( 1 )-hb(read(b,non-null),read(a,XXX))在线程#2中,
  4. 由( 4 )-hb(write(a,non-null),read(b,non-null))
  5. 4 )-hb(write(a,non-null),read(a,XXX))

换句话说,将一个非空值写入a “发生之前”的值(XXX)的读取。 XXX可以为空的唯一方法是,如果有一些其他的操作写入null,使得hb(write(a,non-null),write(a,XXX))和hb(write(a,XXX),读(一,XXX))。 根据问题定义,这是不可能的,因此XXX不能为空。 QED。

解释 – JLS声明hb(…)(“之前发生”)关系并不完全禁止重新sorting。 但是,如果hb(xx,yy),那么只有当结果代码具有与原始序列相同的可观察效果时, 允许对xx和yy进行重新sorting。

我发现下面的post解释了volatile在这种情况下具有与synchronized同样的sorting语义。 Java易失性是强大的

虽然斯蒂芬·C的和接受的答案是好的,几乎涵盖了它,值得重要的一点是variablesa不必是波动的 – 而且你仍然不会得到一个NPE。 这是因为在a = oneb = two之间会有一个发生之前的关系,而不pipea是否是volatile 。 所以斯蒂芬C的formscertificate仍然适用,只是不需要a挥发性的东西。

我读了这个页面 ,发现了一个非易失性和非同步版本的问题:

 class Simple { int a = 1, b = 2; void to() { a = 3; b = 4; } void fro() { System.out.println("a= " + a + ", b=" + b); } } 

对于a的值可以获得1或3,并且可以独立地获得b的值的2或4。

(我意识到这不能回答你的问题,但它补充了它。)