官方便条上说
对易失性字段的写入与监视器释放具有相同的存储效果,而从易失性字段的读取具有与监视器获取相同的存储效果。
和
有效地,易失性的语义得到了实质性的加强,几乎达到了同步的水平。每个易失性字段的读或写操作都类似于“半同步”,以提高可见性。
来自这里。
这是否意味着,对易失性变量的任何写入都会使执行线程将其缓存刷新到主存,而从易失性字段中读取的每一次读取都会使线程从主内存中重新读取其变量?
我之所以问这个问题,是因为同样的文本包含了这句话。
重要注释:注意到,两个线程访问相同的易失性变量以正确设置发生之前的关系是非常重要的。线程A写入易失性字段f时可见的所有内容在读取易失性字段g后都会对线程B可见。发布和获取必须“匹配”(即在同一个易失性字段上执行)才具有正确的语义。
这句话让我很困惑。我确信,对于使用同步语句的常规锁获取和发布来说,这不是真的--如果某个线程释放了任何监视器,那么它所做的所有更改都会明显地影响到所有其他线程(更新:实际上不是真的--查看最佳答案)。在堆栈溢出上甚至有一个对此有疑问。然而,有人指出,无论出于什么原因,不稳定的领域都不是这种情况。我无法想象会发生什么实现--在保证之前,这不会使其他线程、不读取相同易失变量的线程可见更改。至少想象一下一个实现,这与前两个引号并不矛盾。
此外,在发布这个问题之前,我做了一些研究,比如这篇文章,它包含了这句话
执行这些指令后,所有写操作都可以通过缓存子系统或主内存对所有其他线程可见。
提到的指令是在对易失性字段进行写入时发生的指令。
那么这个重要的音符是什么意思呢?还是我遗漏了什么?或者那张纸条完全是错的?
回答?
经过进一步的研究,我只能在官方文件中找到关于易失性字段及其对非易失性域变化的影响的声明:
使用易失性变量降低了内存一致性错误的风险,因为对易失性变量的任何写入都会在与该变量随后读取的关系之前发生。这意味着对易失性变量的更改对其他线程总是可见的。更重要的是,它还意味着当线程读取易失性变量时,它不仅会看到对易失性的最新更改,而且还会看到导致更改的代码的副作用。
来自这里。
我不知道这是否足以得出结论,在只有读取相同易失性的线程才能保证关系之前,这种情况就会发生。因此,就目前而言,我只能概括地说,这些结果是不确定的。
但在实践中,我建议考虑到线程A在写入易失性字段时所做的更改,只有当线程B读取相同的易失性字段时,线程B才能保证对线程B可见。上述来自官方来源的引文强烈地暗示着。
发布于 2018-08-12 12:34:01
你从一个完全错误的角度看这个。首先,您引用了JLS,而不是讨论刷新,这将是该规范的实现细节。您需要依赖的绝对唯一的东西是JLS,任何其他不坏的知道可能是,但不证明正确或错误的规范在任何形式或形式。
你错的地方是:
我可以肯定的是,对于普通的锁获取来说不是真的.
实际上,在x86上,您可能是对的,但是JLS 官方的甲骨文教程规定
当线程释放内部锁时,在该操作与任何后续获取的相同锁之间建立关系之前,就会发生这样的情况。
发生--在为后续操作建立之前(如果需要,请阅读两个操作(如果对您来说比较简单)。一个线程释放锁,另一个线程获得它--它们是后续的(release-acquire semantics)。
对于一个volatile也会发生同样的事情--一些线程会对它进行写入,而当其他一些线程通过后续的读取观察到写入时,就会在建立之前发生。
发布于 2018-08-05 12:29:04
这是否意味着,对易失性变量的任何写入都会使执行线程将其缓存刷新到主存,而从易失性字段中读取的每一次读取都会使线程从主内存中重新读取其变量?
不,这并不意味着。这是一个常见的错误的想法。这意味着在Java内存模型中指定了什么。
在英特尔CPU上,有用于刷新缓存行的指令:clflush和clflushopt,而且每当发生易失性写入时,对整个缓存行进行这种刷新都是非常低效的。
为了提供一个示例,让我们看看易失性变量是如何通过以下方式实现的(对于本例)
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)为了我的哈斯韦尔。让我们编写这个简单的示例:
public static volatile long a = 0;
public static void main(String[] args){
Thread t1 = new Thread(() -> {
while(true){
//to avoid DCE
if(String.valueOf(String.valueOf(a).hashCode()).equals(String.valueOf(System.nanoTime()))){
System.out.print(a);
}
}
});
Thread t2 = new Thread(() -> {
while(true){
inc();
}
});
t1.start();
t2.start();
}
public static void inc(){
a++;
}我禁用了分层编译,并使用C2编译器运行它,如下所示:
java -server -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Volatile.inc -jar target/test-0.0.1.jar输出如下:
# {method} {0x00007f87d87c6620} 'inc' '()V' in 'com/test/Volatlee'
# [sp+0x20] (sp of caller)
0x00007f87d1085860: sub $0x18,%rsp
0x00007f87d1085867: mov %rbp,0x10(%rsp) ;*synchronization entry
; - com.test.Volatlee::inc@-1 (line 26)
0x00007f87d108586c: movabs $0x7191fab68,%r10 ; {oop(a 'java/lang/Class' = 'com/test/Volatlee')}
0x00007f87d1085876: mov 0x68(%r10),%r11
0x00007f87d108587a: add $0x1,%r11
0x00007f87d108587e: mov %r11,0x68(%r10)
0x00007f87d1085882: lock addl $0x0,(%rsp) ;*putstatic a
; - com.test.Volatlee::inc@5 (line 26)
0x00007f87d1085887: add $0x10,%rsp
0x00007f87d108588b: pop %rbp
0x00007f87d108588c: test %eax,0xca8376e(%rip) ; {poll_return}
0x00007f87d1085892: retq
;tons of hlt ommited因此,在这个简单的示例中,volatile编译成locked指令,要求缓存行有一个exclusive状态来执行(如果没有,则可能向其他核心发送读取无效信号)。
https://stackoverflow.com/questions/51693464
复制相似问题