在“&&”操作期间原子值可以改变吗?

Can Atomic values change during an "&&" operation?

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

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 吗?)这将 return “true”,因此 aBoolean 将设置为“false”。

    3.4 aBoolean 现在为 false。

    3.5 丢失线程比较(是布尔值 == true 吗?)这将 return “false”,从而使任何进一步的操作短路。

  4. 获胜线程将打印“Was true!”。

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

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

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

假设线程 #3 刚好到达并执行 unluckySet() 恰好在我们的“获胜线程”到达执行“&&”的“B 行”的那一刻。

如果获胜线程到达“B 行”,则表示它到达了“A 行”,布尔值为“true”。

我的问题是:

CMPXCHG 是否会将更新后的值正确读取为“false”?这意味着 .set() 也与 compareAndSet().

持有相同的锁

在并发和线程之间:

运算符(“&&”、“||”、“==”、“=”,甚至“return;”??)是否在任何纳秒发生,或者它们是否与执行(“;”)以便两者以交错的方式结束,防止可能的冲突?

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

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

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


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

警告:如前一段所述,推理特定机器可能如何工作通常有助于理解为什么可能出现不希望的行为。但它不能替代关于正式内存模型的推理,也不应该用来试图证明程序 必须 具有其所需的行为。即使您心目中的特定机器会为您的代码做正确的事情,或者您想不出一台看似合理的机器会出现故障的方式,也不能证明您的程序是正确的。

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