让我们考虑一下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
,因为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 :(得分: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的更新版本。