易挥发与不易挥发

时间:2017-05-06 19:08:35

标签: java multithreading shared-memory memory-barriers

让我们考虑一下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内存模型保证对32位变量的访问/存储是原子的,因此我们的程序是安全的。但是,我们应该使用属性volatile,因为*。 x的值可能等于1,因为xThread#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。我是否正确理解锁定/解锁互斥锁与插入内存屏障有关?

2 个答案:

答案 0 :(得分:3)

我会尝试解决这个问题。 Java内存模型涉及并且很难包含在单个StackOverflow帖子中。有关完整的故事,请参阅Brian Goetz的 Java Concurrency in Practice

  

x的值总是2,尽管我们不会使它变得不稳定。我是否正确理解锁定/解锁互斥锁与插入内存屏障有关?

首先,如果您想了解Java内存模型,那么您需要通读Chapter 17 of the spec

该规范说:

  

监视器上的解锁发生在该监视器上的每个后续锁定之前。

是的,在显示器解锁时会出现内存可见性事件。 (我假设“mutex”你的意思是监视器。java.utils.concurrent包中的大多数锁和其他类也有发生在之前的语义,请查看文档。)

发生之前是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。阅读时有点令人困惑,但如果你仔细阅读,那么信息就在那里。

答案 1 :(得分:1)

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

我觉得有一些概念是“写回主存”,这是对易失性的常识理解的核心。 Volatile不会将值写回Java中的主内存。读取和写入volatile变量的内容是创建所谓的“先发生过”的关系。当线程#1写入volatile变量时,您正在创建一个关系,该关系确保查看该volatile变量的任何其他线程#2也能够“查看”线程#1之前采取的所有操作。在你的例子中,这意味着让'谁'不稳定。通过将值2写入'who',您正在创建一个before-before关系,这样当线程#2查看who = 2时,它将类似地看到x的更新版本。

在你的第二个例子中(假设你也想要'who'变量),互斥锁解锁会创建一个先前发生的关系,如上所述。由于这意味着其他线程正在查看互斥锁的解锁(即,它们能够自己锁定它们),因此它们将看到x的更新版本。