易失性保证和无序执行
重要的编辑我知道在发生两个任务的线程 “发生之前”我的问题是有可能是另一个线程读取“b”非空,而“a”仍然为空。 所以我知道,如果您从之前调用setBothNonNull(…)的线程调用doIt() ,那么它不能抛出NullPointerException exception 。 但是如果从另一个线程调用doIt() 而不是调用setBothNonNull(…) ?
请注意,这个问题是关于volatile
关键字volatile
保证:它不是关于synchronized
关键字(所以请不要回答“你必须使用同步”,因为我没有任何问题来解决:我只是想了解有关无序执行的volatile
担保(或缺乏担保)。
假设我们有一个包含两个volatile
string引用的对象,这些引用被构造函数初始化为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”?
假设分配给a
和b
或非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 )-hb(write(a,non-null),write(b,non-null))在线程#1
- 由( 2 )和( 4 )-hb(write(b,non-null),read(b,non-null))
- 通过( 1 )-hb(read(b,non-null),read(a,XXX))在线程#2中,
- 由( 4 )-hb(write(a,non-null),read(b,non-null))
- ( 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 = one
和b = 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。
(我意识到这不能回答你的问题,但它补充了它。)