官方说明说,那
写入易失性字段与监视器释放具有相同的记忆效应,从易失性字段读取具有与监视器获取相同的记忆效应.
和
实际上,易失性的语义已大大加强,几乎达到了同步的程度.出于可见性的目的,每次读取或写入易失性字段的行为类似于"半"同步.
从这里开始.
这是否意味着,对volatile变量的任何写入都会使执行线程将其缓存刷新到主内存中,并且每次从volatile字段读取都会使线程从主内存重新读取其变量?
我问,因为同一文本包含此声明
重要说明:请注意,两个线程都必须访问相同的volatile变量才能正确设置before-before关系.情况并非如此,线程A在写入易失性字段f时可见的所有内容在读取易失性字段g后变为线程B可见.释放和获取必须"匹配"(即,在相同的易失性字段上执行)以具有正确的语义.
这句话让我很困惑.我知道对于使用synchronized语句定期锁定获取和释放不是这样 - 如果某个线程释放任何监视器,那么它所做的所有更改都会明显地变为所有其他线程(更新:实际上不是真的 - 请注意最佳答案).在stackoverflow上甚至有一个关于它的问题.然而据说无论出于何种原因,情况并非如此.我无法想象发生在事先保证的任何实现,它不会使其他线程看到更改,这些线程不会读取相同的volatile变量.至少想象一个实现,它与前两个引号并不矛盾.
此外,在发布这个问题之前,我做了一些研究,例如这篇文章,其中包含这个句子
执行这些指令后,所有其他线程都可以通过缓存子系统或主内存看到所有写入.
提到的指令是在写入易失性字段时发生的指令.
那重要的音符应该是什么意思?或者我错过了什么?或许这个说明是完全错误的?
回答?
在进行了一些更多的研究后,我只能在官方文档中找到关于volatile字段及其对非易失性字段变化的影响的声明:
使用volatile变量可降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立与之后读取同一变量的先发生关系.这意味着对volatile变量的更改始终对其他线程可见.更重要的是,它还意味着当线程读取volatile变量时,它不仅会看到volatile的最新更改,还会看到导致更改的代码的副作用.
从这里开始.
我不知道这是否足以得出结论,这种情况发生在关系只保证读取相同的volatile的线程之前.所以现在我只能总结一下结果是不确定的.
但实际上我建议考虑线程A
在写入volatile字段时所做的更改,B
只有当线程B
读取相同的volatile字段时,才能保证线程可见.官方消息来源的上述引言强烈暗示.
对 volatile 字段的写入和读取分别防止在 volatile 字段之前和之后重新排序读/写。写入 volatile 变量之前的变量读/写不能重新排序在它之后发生,而从 volatile 变量读取之后的读/写不能重新排序在它之前发生。但这项禁令的范围是什么?据我了解, volatile 变量只能防止在使用它的块内重新排序,对吗?
为了清楚起见,让我举一个具体的例子。假设我们有这样的代码:
int i,j,k;
volatile int l;
boolean flag = true;
void someMethod() {
int i = 1;
if (flag) {
j = 2;
}
if (flag) {
k = 3;
l = 4;
}
}
Run Code Online (Sandbox Code Playgroud)
显然,write tol
将阻止 write tok
重新排序,但它会阻止重新排序写入i
和j
关于l
? 换句话说,可以写入i
并j
在写入之后发生l
吗?
更新 1
感谢大家花时间回答我的问题 - 我很感激。问题是你回答了错误的问题。我的问题是关于范围,而不是关于基本概念。问题基本上是编译器在代码中保证“发生在之前”与 volatile 字段的关系有多远。显然编译器可以保证在同一个代码块内,但是封闭块和对等块呢 - 这就是我的问题。@Stephen C 说,volatile 保证发生在整个方法主体内部的行为之前,即使在封闭块中,但我找不到任何确认。他说得对吗,有什么地方可以确认吗?
让我再举一个关于范围界定的具体例子来澄清事情: …