线程内一致性

3

这段代码很简单。

// 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

这个问题与是重复的。


1
我投票将此问题标记为不清楚。该问题没有说明涉及一个线程还是两个线程,并且代码不完整。请注意,对于此问题的不同解释,答案可能会非常不同。 - Stephen C
一个最小化可重现的例子会帮助我们更好地理解你实际上在问什么。 - Stephen C
@StephenC 您是正确的,那是我的错误,没有澄清问题,我对此感到非常抱歉。我的问题源自另一段代码,所以我正在尝试简化它,但似乎让我们感到困惑了,我将重新开一个问题来讨论原始代码。 - 梁雨生
最好是你澄清“这个”问题,而不是开另一个问题。这就是编辑按钮的作用... - Stephen C
3个回答

3

任何不用 Java语言规范 解释的答案都只是部分正确,即使正确也是如此。

您需要清楚地区分在单个线程中发生且通过 程序顺序 绑定在一起以创建 happens-before连接 的操作,具体方式是:

    

如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中出现在 y 之前,则 hb(x,y)。

这条规则告诉您,如果您将代码视为单线程环境,则始终会打印 1

另一方面,通过创建跨不同线程的同步连接的操作,隐含地创建了happens-before关系,具体方式如下:

如果一个动作x与后续动作y同步,那么我们也有hb(x,y)。

在您的情况下,I是一个普通字段,因此与它相关的每个操作都是普通存储和/或普通加载。根据JLS,这些存储和加载根本不会创建任何连接。因此,如果涉及写入线程,则某些读取I的线程始终可以将其读取为0


也许我应该解释一下,我的问题源于演讲中展示的double-check if (val == null) {. // point 1 synchronized (this) { if (val == null) { val = new T(); } } } return val; /// point 2 }Aleksey Shipilёv说点1应该与点2一致,这让我感到困惑。 - 梁雨生
很抱歉代码格式不正确。但是你可以在ppt中找到相关内容。 - 梁雨生
1
@梁雨生,但作者在第48页早先解释了一致性是什么。他的观点是val == nullreturn val是_两个独立的读取操作_,不能重新排序,否则双重检查锁定将会失效。详见此处 - Eugene
1
一个对 val 的写操作,必须在 if(val == null)return val 两个读操作中看到相同的值。因此,“一致性”是指:“对单个内存位置的写操作(任何对 val 的写操作)必须按照程序顺序与总顺序一致”。这里的程序顺序是首先 if (val == null),其次是 return val。我知道这并不容易,但也不应该容易。您可以使用 JLS 来正确推理同步程序,而不是使用 JMM - Eugene
嗯,我发现这个问题是重复的,而且我真的搞混了一些东西。在单线程中没有任何数据竞争,因此程序顺序(JLS.17.4.3)可以保证返回值必须为“1”。 - 梁雨生
@ Eugene 是的,也许我应该多读一些并在提问之前先思考,非常感谢您的帮助,您教会了我很多。 - 梁雨生

3
不会,只有调用该方法的线程参与,如果没有其他线程介入,结果为1。
chrylis -cautiouslyoptimistic 的回答值得一读,因为它提供了另一种情况。
两个原因:
1. I 只被其所有者改变,如果另一个线程只调用 test(),则无法获得 0 作为 I 的值。 2. 第二个线程不会读取 Class.I 的值,而是读取 test() 方法的结果。赋值 I=1 发生在返回之前,因此保证提供最新更新的值(仅由所有者更新一次)。

你漏掉了一个情况。 - chrylis -cautiouslyoptimistic-
1
嗯,我只是按照楼主的问题进行操作:“调用test方法的线程”。 你的情况确实正确,但原始问题中没有包含它 :( - aran
1
@chrylis-cautiouslyoptimistic 谢谢您的回答,但问题非常简单,我提出这个问题的目的是为了讨论正如标题所说的“线程内一致性”,因此让我们回到问题本身,暂时忽略其他线程。 - 梁雨生
1
@梁雨生,那么你需要提出一个单一的问题,而不是两个相关但有不同答案的问题。 - chrylis -cautiouslyoptimistic-
1
即使您已经理解了它是涉及“单线程”的,仍需要正确的[JLS引用](https://dev59.com/Br_qa4cB1Zd3GeqPDTwY#65930536);否则,您会做出假设。学习没有错,但这些事情并不是微不足道的,也不应该以微不足道的方式回答。 - Eugene
显示剩余21条评论

2

是的,如果另一个线程在赋值和返回语句之间写入itest方法就有可能返回0:

  1. 线程1:赋值 i = 1
  2. 线程2:赋值 i = 0
  3. 线程1: return i (看到线程2刚刚写的0)

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接