我正在撰写与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
导致a
在v
之前显示 .................(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
(将被刷新到内存中)是可见的。
StoreStore
我是否正确理解它是否有效,因为x86无法重新排序 int tmp = i; // volatile load
// [LoadStore]
// [LoadLoad]
?如果它可能需要额外的内存屏障,是吗?
优秀后@ Eugene的回答:
every action below (after)
在这里,我明白你的意思 - 很明显:int tmp = i
易变读( // [StoreLoad] -- this one
int tmp = i; // volatile load
// [LoadStore]
// [LoadLoad]
)没有被重新排序。
int tmp = i
在这里,你又增加了一个障碍。它确保我们不会使用volatile load
重新排序任何操作。但是,为什么它很重要?为什么我有疑虑?据我所知render() {
return (
<View style={styles.viewStyle}>
<Text style={styles.titleText}>{this.props.Nickname}</Text>
<AutoTypingText
text={"\nAge: " + this.props.Age + "\n"
+ "City: " + this.props.City + "\n"
+ "Recent Login: " + this.props.RecentActivity + "\n"
+ "Points: " + this.props.Points + "\n"
+ "About: \"" + this.props.About + "\"\n"
}
charMovingTime={0}
style={textStyle}
delay={0}
onComplete={() => {
console.log('done');
console.log(this.state)
}}
/>
<View style={{ flexDirection: 'row', justifyContent: 'space-around' }}>
<Button style={styles.buttonStyle} textStyle={styles.buttonText}>
Exploit !
</Button>
<Button style={styles.buttonStyle} textStyle={styles.buttonText}>
Spare
</Button>
</View>
</View>
);
}
}
保证:
在易失性负载可见之前,>> 易失性负载后的每个操作都不会被重新排序。
我看到你写道:
需要有顺序一致性
但是,我不明白为什么需要顺序一致性。
答案 0 :(得分:4)
有些事情,首先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
这两个障碍;用简单的英语表示Load
之后的任何Store
和volatile load/read
都无法向前移动。屏障,他们不能重新订购&#34;以上&#34;挥发性负荷。
以下是volatile store
的示例。
// "i" is a shared volatile variable
// [StoreStore|LoadStore]
i = tmp; // volatile store
这意味着任何Load
和Store
都无法进入&#34;低于&#34;加载存储本身。
这基本上构建了之前发生的关系,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
上x86
的一个不错的选择,但同样的事情可以通过lock add
保证(AFAIK in a {更便宜的方式),这就是为什么你在那里看到它。基本上是 StoreLoad
障碍。是的 - 你的最后一句话是正确的,对于较弱的记忆模型 - 需要StoreStore
障碍。在侧面说明,即通过构造函数中的final
字段安全地发布引用时使用的内容。退出构造函数后,会插入两个栅栏:LoadStore
和StoreStore
。
把这一切都拿出来 - 只要它没有违反任何规则,JVM可以自由地忽略这些:Aleksey Shipilev对此有一个很好的讨论。
修改强>
假设你有这种情况:
[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
来点btw(如果我没记错的话)。 使用volatile本身无法重新排序 - 其他操作可以自由重新排序。让我举个例子:
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 store