我写这篇文章是为了联系Deep understanding of volatile in Java
public class Main {
private int x;
private volatile int g;
public void actor1(){
x = 1;
g = 1;
}
public void actor2(){
put_on_screen_without_sync(g);
put_on_screen_without_sync(x);
}
}现在,我正在分析JIT为上面的代码生成了什么。从我们在上一篇文章中的讨论中可以了解到,输出1, 0是不可能的,因为:
写入易失性v会导致a先于v的每个操作都会导致在v可见之前a是可见的(将被刷新到内存中)。
.................(I removed not important body of method).....
0x00007f42307d9d5e: c7460c01000000 (1) mov dword ptr [rsi+0ch],1h
;*putfield x
; - package.Main::actor1@2 (line 14)
0x00007f42307d9d65: bf01000000 (2) mov edi,1h
0x00007f42307d9d6a: 897e10 (3) mov dword ptr [rsi+10h],edi
0x00007f42307d9d6d: f083042400 (4) lock add dword ptr [rsp],0h
;*putfield g
; - package.Main::actor1@7 (line 15)
0x00007f42307d9d72: 4883c430 add rsp,30h
0x00007f42307d9d76: 5d pop rbp
0x00007f42307d9d77: 850583535116 test dword ptr [7f4246cef100h],eax
; {poll_return}
0x00007f42307d9d7d: c3 ret我是否正确地理解它的工作,因为x86不能使StoreStore重新排序?如果可以的话,还需要额外的内存屏障,是吗?
编辑后,优秀@尤金的回答:
int = i;//易失性负载// LoadStore // LoadLoad
在这里,我明白了您的意思-很明显:every action below (after)易失性读取(int tmp = i)不会被重新排序。
// StoreLoad
在这里,你再设置一个屏障。它确保了int tmp = i不会对任何操作进行重新排序。但是,为什么它是重要的?为什么我会怀疑?据我所知,volatile load保证:
在可见易失性负载之前,在易失负载之后的每个动作都不会被重新排序。
我看到你在写:
需要有一个连续的一致性
但是,我不明白为什么需要顺序一致性。
发布于 2017-07-18 08:35:07
有几件事,首先是will be flushed to memory --这是非常错误的。它几乎从来都不会流到主内存--它通常会将StoreBuffer耗尽到L1,并且需要由缓存一致性协议来同步所有缓存之间的数据,但是如果用这些术语更容易理解这个概念,那就好了--只要知道这有点不同,而且速度更快。
这是一个很好的问题,为什么[StoreLoad]在那里确实,也许这将澄清一些事情。volatile实际上都是关于栅栏的,这里是一个例子,说明在某些不稳定的操作中会插入什么障碍。例如,我们有一个volatile load
// i is some shared volatile field
int tmp = i; // volatile load of "i"
// [LoadLoad|LoadStore]注意这里的两个障碍,LoadStore和LoadLoad;在通俗的英语中,这意味着在volatile load/read之后的任何Load和Store都不能“向上”移动屏障,它们不能被重新排序“高于”那个易失的负载。
下面是volatile store的例子。
// "i" is a shared volatile variable
// [StoreStore|LoadStore]
i = tmp; // volatile store这意味着任何Load和Store都不能“低于”加载存储本身。
这基本上建立了发生之前的关系,volatile load是获取负载,volatile store是发布存储(这也与Store和Load cpu缓冲区的实现方式有关,但这几乎是不可能的)。
如果你想一想,我们对volatile的了解是非常有意义的;它说,一旦一个易失性存储被一个易失性负载所观察到,volatile store之前的所有东西也会被观察到,这与内存屏障是一样的。现在有道理了,当一个易失性存储发生时,它上面的所有东西都不能超越它,一旦发生了不稳定负载,它下面的所有东西都不能超过它,否则就会发生
但不是这样,还有更多。需要有顺序的一致性,这就是为什么任何理智的实现都将保证挥发物本身不被重新排序,因此又插入了两个栅栏:
// any store of some other volatile
// can not be reordered with this volatile load
// [StoreLoad] -- this one
int tmp = i; // volatile load of a shared variable "i"
// [LoadStore|LoadLoad]这里还有一个:
// [StoreStore|LoadStore]
i = tmp; // volatile store
// [StoreLoad] -- and this one现在,事实证明,在x86上,4个内存屏障中有3个是免费的--因为它是一个strong memory model。唯一需要实现的是StoreLoad。例如,在其他CPU上,比如ARM,lwsycn是一种使用的指令--但我对它们不太了解。
通常,mfence对于StoreLoad on x86来说是一个很好的选择,但是通过lock add (AFAIK以一种更便宜的方式)来保证同样的事情,这就是你在那里看到它的原因。基本上,这就是StoreLoad屏障。是的--对于一个较弱的记忆模型,你的最后一句话是对的-- StoreStore屏障是必需的。另外,当您通过构造函数中的final字段安全地发布引用时,请注意这是使用的。在退出构造函数时,插入了两个栅栏:LoadStore和StoreStore。
只要不违反任何规则,JVM就可以随意忽略这些问题:对此有一个很好的讨论。
编辑
假设您有这样的情况:
[StoreStore|LoadStore]
int x = 4; // volatile store of a shared "x" variable
int y = 3; // non-volatile store of shared variable "y"
int z = x; // volatile load
[LoadLoad|LoadStore]基本上,没有任何障碍可以阻止volatile store与volatile load重新排序(即:首先执行易失性负载),这将导致明显的问题;顺序一致性因此被违反。
您在Every action after volatile load won't be reordered before volatile load is visible中有点漏掉了这里的要点(如果我没有弄错)。重新排序是不可能与易失性本身-其他操作是自由的重新排序。让我举一个例子:
int tmp = i; // volatile load of a shared variable "i"
// [LoadStore|LoadLoad]
int x = 3; // plain store
int y = 4; // plain store最后两个操作x = 3和y = 4完全可以重新排序,它们不能浮动在易失性之上,但它们可以通过自身重新排序。上述例子是完全合法的:
int tmp = i; // volatile load
// [LoadStore|LoadLoad]
// see how they have been inverted here...
int y = 4; // plain store
int x = 3; // plain storehttps://stackoverflow.com/questions/45151763
复制相似问题