问题描述
代码很简单。
// not annotated with volatile
public static int I = 0;
public static int test(){
I = 1;
return I;
}
方法 test
是否有可能返回值“0”?
更新
a thread
的意思是 a single thread
。
问题与 it 重复。
解决方法
不,它将是 1 如果不涉及其他线程,但会调用该方法的线程。
chrylis -cautiouslyoptimistic 的回答对于替代方案也值得一读。
两个原因:
-
I
只是被它的所有者改变了,如果另一个线程只是调用了 test(),它就没有选择将 0 作为I
的值。 - 第二个线程不会读取
Class.I
的值,而是test()
方法的结果。分配I=1
发生在返回之前,因此保证提供最新的更新值(仅由所有者更新一次)。
任何没有解释 java language specification 的答案都只是部分正确,如果完全正确的话。
您需要明确区分发生在单个线程中并由 program order 连接在一起的操作,这反过来又创建了一个 happens-before connection,具体通过:
如果 x 和 y 是同一个线程的动作,并且 x 在程序顺序中排在 y 之前,则 hb(x,y)。
该规则告诉您,如果您在单线程世界中考虑此代码,它将始终打印 1
。
另一方面,创建跨不同线程的synchronizes with连接的操作,以及隐式创建的操作,先发生,通过:
如果一个动作 x 与随后的动作 y 同步,那么我们也有 hb(x,y)。
在您的情况下,I
是一个 plain 字段,因此与它相关的每个操作都是一个 plain store 和/或一个 plain加载。根据 JLS
,此类存储和加载根本不会创建任何连接。因此,如果涉及写入线程,读取 I
的某些线程始终可以将其读取为 0
。
是,如果另一个线程在赋值和返回语句之间写入 test
,则 i
方法可能返回 0:
- 线程 1:分配
i = 1
- 线程 2:分配
i = 0
- 线程 1:
return i
(看到线程 2 刚刚写入的 0)
为防止这种情况发生,所有对 i
的访问、读取和写入都需要在相同条件下同步。将 i
设为 volatile 不足以防止线程轮流修改它。
请注意,并不是线程 1“没有看到”i = 1
写入;这是有保证的,因为所有语句都按程序顺序在逻辑上执行。但是,另一个线程可能会在写入发生之后但在线程 1 读取它之前更改该值。