线程内一致性

问题描述

代码很简单。

// not annotated with volatile
public static int I = 0;

public static int test(){
    I = 1;
    return I;
}

一个线程调用 test 方法

方法 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. 线程 1:分配 i = 1
  2. 线程 2:分配 i = 0
  3. 线程 1:return i(看到线程 2 刚刚写入的 0)

为防止这种情况发生,所有对 i 的访问、读取和写入都需要在相同条件下同步。将 i 设为 volatile 不足以防止线程轮流修改它。

请注意,并不是线程 1“没有看到”i = 1 写入;这是有保证的,因为所有语句都按程序顺序在逻辑上执行。但是,另一个线程可能会在写入发生之后但在线程 1 读取它之前更改该值。