挥发性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
。我是否正确理解,锁定/解锁互斥体是通过插入内存屏障来连接的?
我会尽力解决这个问题。 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。通读它有点令人困惑,但如果你仔细阅读,信息就会全部存在。
@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的更新版本。
“由其他线程查看”在这里是关键。 *线程中的语句*保证按程序顺序执行(它们在代码中出现的顺序)。内存写入的可见性*由其他线程看到*根本无法保证。需要互斥或'volatile'来完成关于线程2在“可能”之外看到的任何声明。 – markspace
也是“写回主内存的概念”:是的。 Java是为硬件编写的,它不一定是缓存一致的。 Brian Goetz在* Java Concurrency in Practice *中谈论了Java设计的这一方面,并说Java是为这种CPU设计的。这有点奇怪,但很容易习惯。然而,'volatile'明确地*会创建一个“flush到主内存”,这就是发生的事情 - 之前的意思(在其他一些事情中)。 – markspace
从功能上来说,我不确定'刷到主内存'是正确的方式来说明发生了什么。我的理解是,易失性通常是作为内存屏障实施的。这些结构不一定会导致CPU缓存刷新;他们只是限制了如何发生内存重新排序,这会影响处理器如何看待数据。 –
请发布一个MCVE。 –
什么是MCVE? – Gilgamesz
阅读关于它[这里](https://*.com/help/mcve) –