挥发性与非挥发性

volatile vs not volatile

让我们考虑 Java

中的以下代码
int x = 0;
int who = 1
Thread #1:
   (1) x++;
   (2) who = 2;

Thread #2
   while(who == 1);
   x++;   
   print x; ( the value should be equal to 2 but, perhaps, it is not* )    

(我不知道 Java 内存模型-假设它是强内存模型-我的意思是:(1) 和 (2) 不会交换)
Java 内存模型保证 access/store 到 32 位变量是原子的,因此我们的程序是安全的。但是,尽管如此我们还是应该使用属性 volatile 因为 *. x的值可能等于1,因为x可以在Thread#2读取时保存在寄存器中。要解决它,我们应该使 x 变量 volatile。清楚了。

但是,那种情况呢:

    int x = 0;
    mutex m; ( just any mutex)
Thread #1:
       mutex.lock()
       x++;
       mutex.unlock()

    Thread #2
       mutex.lock()
       x++;   
       print x; // the value is always 2, why**?
       mutex.unlock()

x 的值始终是 2,尽管我们没有做到 volatile。我是否正确理解 locking/unlocking 互斥锁与插入内存屏障有关?

让我们回顾一些事情。下面的说法是正确的: Java 内存模型保证 access/store 到 32 位变量是原子的。但是,这并不意味着您列出的第一个伪程序是安全的。仅仅因为两个语句在句法上是有序的 而不是 就意味着它们的更新的可见性也是按照其他线程查看的顺序排列的。线程 #2 可能会在 x 的增量可见之前看到由 who=2 引起的更新。使 x 易变仍然不会使程序正确。相反,使变量 'who' 易变会使程序正确。这是因为 volatile 以特定方式与 java 内存模型交互。

我觉得在对 volatile 的常识性理解的核心存在一些 'writing back to main memory' 概念,这是不正确的。 Volatile 不会将 Java 中的值写回主存。读取和写入 volatile 变量的作用是创建所谓的 happens-before 关系。当线程 #1 写入一个 volatile 变量时,您正在创建一种关系,以确保查看该 volatile 变量的任何其他线程 #2 也将能够 'view' 线程 #1 在此之前采取的所有操作。在您的示例中,这意味着使 'who' 不稳定。通过将值 2 写入 'who',您正在创建一个 happens-before 关系,以便当线程 #2 查看 who=2 时,它同样会看到 x 的更新版本。

在你的第二个例子中(假设你也想拥有 'who' 变量)互斥锁解锁创建了一个 happens-before 关系,正如我在上面指定的那样。因为这意味着其他线程查看互斥锁的解锁(即它们能够自己锁定它)它们将看到 x 的更新版本。

我会尽力解决这个问题。 Java 内存模型有点复杂,很难包含在单个 Whosebug post 中。请参阅 Brian Goetz 的 Java 并发实践 了解完整故事。

The value of x is always 2 though we don't make it volatile. Do I correctly understand that locking/unlocking mutex is connected with inserting memory barriers?

首先如果你想了解Java内存模型,你总是Chapter 17 of the spec要通读。

该规范说:

An unlock on a monitor happens-before every subsequent lock on that monitor.

是的,在您的显示器解锁时有一个内存可见性事件。 (我假设 "mutex" 你的意思是监视器。java.utils.concurrent 包中的大多数锁和其他 类 也有 happens-before 语义,检查文档。)

Happens-before 是 Java 的意思,它不仅保证事件有序,而且保证内存可见性。

We say that a read r of a variable v is allowed to observe a write w
to v if, in the happens-before partial order of the execution trace:

    r is not ordered before w (i.e., it is not the case that 
    hb(r, w)), and

    there is no intervening write w' to v (i.e. no write w' to v such
    that hb(w, w') and hb(w', r)).

Informally, a read r is allowed to see the result of a write w if there
is no happens-before ordering to prevent that read. 

全部来自17.4.5。通读起来有点混乱,但是如果您通读它,信息就在那里。