返回信息流| 屏障类型 | 指令示例 | 说明 |
|:---|:---|:---|
| LoadLoad Barriers | Load1;LoadLoad;Load2 | 确保Load1数据的装载先于Load2及所有后续装载指令的装载 |
| StoreStore Barriers | Store1;StoreStore;Store2 | 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储 |
| LoadStore Barriers | Load1;LoadStore;Store2 | 确保Load1数据装载先于Store2及所有后续的存储指令刷新到内存 |
| StoreLoad Barriers | Store1;StoreLoad;Load2 | 确保Store1数据对其他处理器变得可见(指刷新到内存)先于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令 |
* StoreLoad Barriers是一个全能型屏障,他同时具有其他3个屏障的效果。现代处理器大多数支持该屏障。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)
__问题1__:仅从StoreLoad的描述来看,为什么说StoreLoad同时具有其他3个屏障的效果?
__volatile写的内存语义__
* 当写一个volatile变量时,JVM会把该线程对应的本地内存中的共享变量值刷新到主内存
__volatile读的内存语义__
* 当读一个volatile变量时,JVM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量
JMM对volatile内存语意的实现满足如下规则
__volatile重排规则表__
| 第一个操作/第二个操作 | 普通读/写 | volatile读 | volatile 写|
|:---|:---|:---|:---|
| 普通读/写 | YES | YES | NO |
| volatile读 | NO | NO | NO |
| volatile 写| YES | NO | NO |
__问题2:__
* 如果第一个操作是普通读写操作,第二个操作是volatile写操作,为什么要禁止重排?
* 如果第一个操作是volatile写操作,第二个操作是普通读写操作,为什么不禁止重排?
* 如果第一个操作是volatile读操作,第二个操作是普通读写操作,为什么要禁止重排?
* 如果第一个操作是普通读写操作,第二个操作是volatile读操作,为什么不禁止重排?
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障(lock addl)来禁止特定类型的处理器重排序。对于编译器来说,__发现一个最优布置来最小化插入屏障的总数几乎是不可能的__。因此,JVM采取保守策略
1. 在每个volatile __写__ 操作 __前面__ 插入一个 __StoreStore__ 屏障
1. 在每个volatile __写__ 操作 __后面__ 插入一个 __StoreLoad__ 屏障
1. 在每个volatile __读__ 操作 __后面__ 插入一个 __LoadLoad__ 屏障
1. 在每个volatile __读__ 操作 __后面__ 插入一个 __LoadStore__ 屏障
__问题3:__上述提到的第1条和第2条关于volatile写内存语意实现方式,这两条内存屏障插入规则如何禁止`volatile重排序规则表`中提到的禁止普通读与volatile写的重排序?
# !!!各路大神,跪求解答!!!
这是一条镜像帖。来源:北邮人论坛 / java / #56739同步于 2017/7/7
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
【问题】Java volatile
liuyehcf
2017/7/7镜像同步16 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
问题2:
如果第一个操作是普通读写操作,第二个操作是volatile写操作,为什么要禁止重排?
```java
public class VolitaleTest {
private int a=0;
private boolean flag=false;
public void wakeUp(){
a=1;
flag=true;
}
public int read(){
while (true){
if (flag){
return a*a;
}
}
}
}
```
1. 假如线程1 执行wakeUp 操作,flag=true时?a=1?
答案是否定的,由于flag,a没有数据依赖关系,所以执行顺序有可能是
flag=true;a=1;
2. 相应的,同时线程2执行read操作,if(flag) 发现flag=true了,a有可能还是0.
3. 所以flag声明为volitale,此时的禁止重排序,保证了此时线程2读到的a=1。
如果你看的文档提到“局部内存”和“全局内存”,就不要读了,那是关于老的java memory model的。新的(java1.5以后)memory model只提happens before关系,不再区分global memory和local memory。那种“刷新到内存”的说法也不用再考虑了。
问题2:
如果第一个操作是volatile读操作,第二个操作是普通读写操作,为什么要禁止重排?
以上述为例,保证了
a*a 不会排到 if(flag) 前面
【 在 nuanyangyang 的大作中提到: 】
: 如果你看的文档提到“局部内存”和“全局内存”,就不要读了,那是关于老的java memory model的。新的(java1.5以后)memory model只提happens before关系,不再区分global memory和local memory。那种“刷新到内存”的说法也不用再考虑了。
总觉得happens before过于抽象 ,不容易实际映射。
java语言本来就是硬件的抽象。不同的硬件的模型不一样,不一定都能直接对应java的模型。
如果一定要比较形象的比喻,可以想象这样的模型:整个机器有一个全局的内存,没有局部内存。每个处理器(即:线程)直接连接到全局内存。每个处理器和内存之间有一个队列,处理器通过自己的队列向内存发送读写请求。每个处理器发出的读写请求顺序和程序顺序一致。但队列里,相邻的两个请求(非volatile),不论是读还是写,如果对的是不同地址,都可以自由换位,这会使得请求到达内存的顺序和发出的顺序不一致。全局内存每次任选一个队列,从中取第一个请求处理,即按不一定的顺序串行处理,但要尊重队列。volatile请求和普通请求的区别就是其他任何相邻的请求都不能与它互换顺序。
于是这样的抽象机器的可能的执行结果,就是java memory model允许的执行结果。
【 在 EMyuan 的大作中提到: 】
:
重新看到暖神的回复,迷之感动。。。
暖神可以推荐一些关于memory model的资料吗
【 在 nuanyangyang 的大作中提到: 】
: 如果你看的文档提到“局部内存”和“全局内存”,就不要读了,那是关于老的java memory model的。新的(java1.5以后)memory model只提happens before关系,不再区分global memory和local memory。那种“刷新到内存”的说法也不用再考虑了。
非常感谢!!!
volatile写 与 普通读写 不禁止重排的原因是什么呢?是因为不会出现你的例子中出现的那种情况?
【 在 EMyuan 的大作中提到: 】
: 问题2:
: 如果第一个操作是普通读写操作,第二个操作是volatile写操作,为什么要禁止重排?
: ```java
: ...................
入门的话看 http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-95-7.pdf
然后看hans boehm的论文,一个是关于java memory model的,另一个是关于c++11 memory model的。
关于实现,看 https://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
关于形式证明,以及怎么构造出上述的类似机器的队列和请求的模型,看上述剑桥大学的研究组的论文。
【 在 liuyehcf 的大作中提到: 】
: 重新看到暖神的回复,迷之感动。。。
:
: 暖神可以推荐一些关于memory model的资料吗