挥发性VS不挥发

问题描述:

让我们看看下面这段代码在Java中挥发性VS不挥发

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,因为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。我是否正确理解,锁定/解锁互斥体是通过插入内存屏障来连接的?

+0

请发布一个MCVE。 –

+0

什么是MCVE? – Gilgamesz

+1

阅读关于它[这里](https://*.com/help/mcve) –

我会尽力解决这个问题。 Java内存模型是一种涉及并很难包含在一个单一的*后。请参阅Brian Goetz的Java并发实践了解全部内容。

x的值始终为2,尽管我们不会使其变得不稳定。我是否正确理解,锁定/解锁互斥体是通过插入内存屏障来连接的?

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

即规范说:

解锁在显示器的之前发生在该显示器上每一个后续的锁。

所以是的,在显示器解锁时会出现内存可见性事件。 (我所说的“互斥”假设你的意思监测,大部分在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。通读它有点令人困惑,但如果你仔细阅读,信息就会全部存在。

+0

@scottb那么他似乎在问关于互斥/监视器。 “易变”只是他前提的一部分。当然,我们经常可以在这里回答具体的问题,但理解完整的内存模型对于在Java中良好编程是非常必要的。例如,很多人都知道'volatile'具有什么作用,但很少有人知道'final'的作用。 (提示:阅读规范的第17章!!) – markspace

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

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

在你的第二个例子中(假设你也打算使用'who'变量),互斥锁解锁会创建一个before-before关系,如上所述。因为这意味着其他线程查看互斥锁的解锁(即他们能够自己锁定),他们将看到x的更新版本。

+1

“由其他线程查看”在这里是关键。 *线程中的语句*保证按程序顺序执行(它们在代码中出现的顺序)。内存写入的可见性*由其他线程看到*根本无法保证。需要互斥或'volatile'来完成关于线程2在“可能”之外看到的任何声明。 – markspace

+0

也是“写回主内存的概念”:是的。 Java是为硬件编写的,它不一定是缓存一致的。 Brian Goetz在* Java Concurrency in Practice *中谈论了Java设计的这一方面,并​​说Java是为这种CPU设计的。这有点奇怪,但很容易习惯。然而,'volatile'明确地*会创建一个“flush到主内存”,这就是发生的事情 - 之前的意思(在其他一些事情中)。 – markspace

+0

从功能上来说,我不确定'刷到主内存'是正确的方式来说明发生了什么。我的理解是,易失性通常是作为内存屏障实施的。这些结构不一定会导致CPU缓存刷新;他们只是限制了如何发生内存重新排序,这会影响处理器如何看待数据。 –