原子值可以在"&&"操作期间更改吗?



我知道下一个场景:(奇怪的格式,我知道)

private final AtomicBoolean aBoolean = new AtomicBoolean(true);
public void doSomething() {
if (
aBoolean.get()                       // line A
&&                               // line B
aBoolean.compareAndSet(true, false)  // line C
) {
System.out.println("Was true!")
}
}

如果线程 #1 和线程 #2 同时输入doSomething(),则会发生这种情况:

线程 #
  1. 1 和线程 #2 将aBoolean.get()读作 == 同时"真"。

  2. 两者都将执行"&&"运算符。

  3. CMPXCHG 指令同时启动两个线程:

    3.1 LOCK 前缀是原生使用的

    3.2 线程 #1 或 #2 首先到达,赢得比赛。

    3.3 获胜线程比较(aboolean == true吗?)这将返回"true",因此abooian将被设置为"false"。

    3.4 aboolean 现在是假的。

    3.5 丢失线程比较(aboolean == true 吗?)这将返回"false",因此会使任何进一步的操作短路。

  4. 获胜线程将打印"是真的!

在"失败"线程的视角下,"A 行"中的第一个aBoolean.get()是......比方说...一个"谎言"。

现在,假设执行可以在运算符之间发生,就像上面的例子一样,让我们为第二个场景添加第二个方法:

public void unluckySet() {
aBoolean.set(false);
}

假设线程 #3 恰好在我们的"获胜线程"到达"&&"正在执行的"B 行"的确切时刻到达并执行unluckySet()

如果获胜线程到达"行 B",则意味着它到达了"行 A",而 aboolean 是"true"。

我的问题是:

CMPXCHG 是否会将更新的值正确读取为"假"?,这意味着.set()也由与compareAndSet()相同的锁持有。

在并发和线程之间:

do 运算符 ("&&", "||"、"=="、"=",甚至可能是"返回;"??)在任何纳秒发生,或者它们是否与处决交错(";") 以便两者以交错方式结束,防止可能的碰撞?

Java 内存模型在没有数据争用(您的程序没有)的情况下是顺序一致性。 这是非常强大的;它说程序中的所有读取和写入都形成一个总顺序,这与程序顺序一致。 因此,您可以想象来自不同线程的读取和写入只是以某种方式交错或混杂在一起 - 而不会更改从同一线程彼此执行的操作的相对顺序。

但出于此目的,每个操作都是此顺序中的单独元素。 因此,仅仅通过aBoolean.get()aBoolean.compareAndSet()两个操作而不是一个操作这一事实,其他线程就有可能在它们之间发生任意数量的其他操作。

这些操作是单个语句的一部分,还是不同语句的一部分;或者它们出现在什么样的表达式中;或者它们之间的运算符(如果有的话);或者线程本身可能会或可能不会围绕它们进行哪些计算,这并不重要。 两个动作不可能"如此接近",以至于两者之间不会发生任何其他事情,除非用语言定义为原子的单个动作代替它们。


在机器级别,一种非常简单的发生方式是,由于aBoolean.get()aBoolean.compareAndSet()几乎可以肯定是两个不同的机器指令,因此它们之间可能会发生中断。 此中断可能会导致线程延迟任意时间,在此期间其他线程可以执行任何它们希望的操作。 因此,线程 #1 和 #2 完全有可能在它们的get()compareAndSet()之间被中断,并且线程 #3 同时执行其set

注意:推理特定计算机的工作方式通常有助于理解为什么会出现不希望的行为,如上一段所述。 但它不能替代关于形式内存模型的推理,也不应该被用来试图论证程序必须有其期望的行为。 即使你心目中的一台特定的机器会为你的代码做正确的事情,或者你想不出一台合理的机器会失败的方式,这并不能证明你的程序是正确的。

因此,试图说"哦,机器会做一个lock cmpxchg,所以等等等等,一切都可以工作"是不明智的;其他一些你没有想到的机器可能会以完全不同的方式工作,它仍然符合抽象的Java内存模型,但违反了你基于x86的期望。 事实上,x86 是一个特别糟糕的例子:由于历史原因,它提供了一组相当强大的指令级内存排序保证,而许多其他"弱序"架构没有,因此可能有很多事情是 Java 抽象地允许的,但 x86 在实践中不会做。

最新更新