use*_*818 5 java multithreading
许多人问这样的类似问题,但他们的答案都没有让我满意.我非常确定的唯一两个重新排序规则如下:
但是,对于同步块之前或之后的那些操作,它们是否可以移动到临界区?对于这个问题,我发现有些相反.例如,cookbook说编译器会在MonitorEnter之后和MonitorExit之前插入一些障碍:
MonitorEnter
(any other needed instructions go here )
[LoadLoad] <===MB1:Inserted memory barrier
[LoadStore] <===MB2:Inserted memory barrier
(Begin of critical section)
....
(end of critical section)
[LoadStore] <===MB3:Inserted memory barrier
[StoreStore] <===MB4:Inserted memory barrier
(any other needed instructions go here )
MonitorExit
Run Code Online (Sandbox Code Playgroud)
根据以上编译器的位置并给出下面的伪代码:
Load a;
Load b;
Store 1;
Store 2;
MonitorEnter
(any other needed instructions go here )
[LoadLoad] <===MB1
[LoadStore] <===MB2
(Begin of critical section)
....
(end of critical section)
[LoadStore] <===MB3
[StoreStore] <===MB4
(any other needed instructions go here )
MonitorExit
Store 3;
Store 4;
Load c;
Load d;
Run Code Online (Sandbox Code Playgroud)
根据这种XY(X是加载或存储,Y是加载或存储)内存障碍强制执行的食谱和重新排序规则,在我看来,有效/无效的重新排序如下:
理解1:MonitorExit之后的任何存储(Store 3和Store 4)都不能在MB3和MB4之前向上移动,因为存在LoadStore(MB3)后跟StoreStore(MB4).这就是说MonitorExit之后的商店无法进入关键部分.但是,它可以在MB4之后向上移动,即支架区域.
理解2:由于存在LoadLoad(MB1)后跟LoadLoad(MB2),所以在MonitorEnter之后的任何加载(此处加载a和加载b)都不能在MB2和MB1之后向下移动.这就是说之前的加载MonitorEnter无法移动到关键位置.但是,它可以在MB2之后向下移动,即支架区域.
理解3:MonitorExit之后的任何加载(Load c和Load d here)都可以在MonitorExit之前向上移动,包括临界区和括号区,但不能超过MonitorEnter.
理解4:MonitorEnter之前的任何存储(存储1和存储2)都可以在MonitorEnter之后向下移动,包括临界区和括号区,但不能超过MonitorExit.
然而,所有上述理解或主张都与Jeremy Manson在他的博客中所说的相反,他声称在下面的代码中给出了以下代码:
x = 1;//Store
synchronized(o) {
z = z + 1;
}
y = 1//Store
Run Code Online (Sandbox Code Playgroud)
产生下面的代码重新排序是允许的:
synchronized(o){
y = 1;//I added this comment:Store moved inside the critical section
z = z + 1;
x = 1;//I added this comment:Store moved inside the critical section
}
Run Code Online (Sandbox Code Playgroud)
根据理解1,"y = 1"不能在临界区内移动,所以我只是感到困惑,哪一个是正确和完整的?
重新排序不关心内存障碍。即使编译器始终在任意两条指令之间插入最强的内存屏障,无论如何仍然允许这些重新排序。
现在,给定一个指令序列,可能在从原始序列重新排序之后,编译器需要在某些指令之间插入适当的内存屏障。
例如,给定原始指令序列
volatile store x
normal store y
Run Code Online (Sandbox Code Playgroud)
两条指令之间不需要内存屏障。
但是,编译器可能会选择将其重新排序为
normal store y
volatile store x
Run Code Online (Sandbox Code Playgroud)
那么两条指令之间需要一个 StoreStore 屏障。CPU只有一条“存储”指令,没有普通/易失性存储的概念。并且CPU可能存在乱序存储。Java 语义要求另一个 CPU 不能store x在 volatile 的作用之前观察到 的作用store y;所以 StoreStore 是用来告诉 CPU 按顺序存储它们的。
(如果编译器足够聪明,它会记住原始程序不需要 的排序y->x,因此实际上不需要这个屏障。但是假设编译器不是那么聪明。)
罗奇汽车旅馆模型——
JMM 的要点是在不同线程上的指令之间建立一些(部分)顺序,以便可以定义读/写的效果。在下面的示例中,
thread 1 thread 2
a1 a2
|
}b1 -----> b2{
|
c1 c2
Run Code Online (Sandbox Code Playgroud)
b1->b2建立同步顺序,可以是volatile store -> volatile load、 或monitor exit -> monitor enter。这a1->b1->b2->c2按照先发生的顺序连接。
由于我们需要保证 的排序a1->c2,a1因此不得与 重新排序b1,并且c2不得与 重新排序b2;也就是说,蟑螂无法“签出”。
另一方面,JMM希望尽可能弱;c1它没有提及和之间的影响a2,b2,c2;因此,c1可以使用 自由重新排序b1。同样,a2可以使用 重新排序b2。也就是说,蟑螂可以“签到”。