繁体   English   中英

什么是Java同步的有效重新排序?

[英]What are valid reordering for Java synchronized?

许多人问这样的类似问题,但他们的答案都没有让我满意。 非常确定的唯一两个重新排序规则如下:

  1. 只要它确认为as-if-serial语义,就允许对synchronized块内的操作(​​或者只是调用它的关键部分)进行重新排序。
  2. 不允许将操作(包括读取和写入)移动(重新排序)到关键部分之外。

但是,对于同步块之前或之后的那些操作,它们是否可以移动到临界区? 对于这个问题,我发现有些相反。 例如, 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

根据以上编译器的位置并给出下面的伪代码:

  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;

根据这样的XY(X是加载或存储,Y是加载或存储)内存障碍强制执行的烹饪书和重新排序规则,在我看来,有效/无效的重新排序如下:

理解1:MonitorExit之后的任何存储(Store 3和Store 4)都不能在MB3和MB4之前向上移动,因为存在LoadStore(MB3)后跟StoreStore(MB4)。 这就是说MonitorExit之后的商店无法进入关键部分。 但是,它可以在MB4之后向上移动,即支架区域。

理解2:由于存在LoadLoad(MB1)后跟LoadLoad(MB2),因此在MB2和MB1之后不能向下移动MonitorEnter之前的任何加载(此处加载a和加载b)。这就是说在加载之前加载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

产生下面的代码重新排序允许:

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
}

根据理解1,“y = 1”不能在临界区内移动,所以我只是感到困惑,哪一个是正确和完整的?

重新排序不关心内存障碍。 即使编译器总是在任何两条指令之间插入最强的内存屏障,仍然允许这些重新排序。

现在,给定一系列指令,可能在从原始序列重新排序之后,编译器需要在某些指令之间插入适当的内存屏障。

例如,给出原始的指令序列

volatile store x
normal   store y

它不需要两个指令之间的内存屏障。

但是,编译器可能会选择将其重新排序

normal   store y
volatile store x

然后在两条指令之间需要一个StoreStore屏障。 CPU只有一个“存储”指令,没有普通/易失性存储的概念。 并且CPU可能具有无序存储。 Java语义要求另一个CPU在volatile store y的影响之前不能观察到store x的影响; 所以StoreStore用于告诉CPU按顺序存储它们。

(如果编译器足够智能,它会记住原始程序不需要y->x的排序,因此实际上并不需要这个障碍。但是让我们说编译器并不那么聪明。)


罗奇汽车旅馆型号 -

JMM的要点是在不同线程上的指令之间建立一些(部分)顺序,以便可以定义读/写的效果。 在以下示例中,

thread 1             thread 2

  a1                   a2
  |
 }b1       ----->      b2{
                       |
  c1                   c2

建立同步命令b1->b2 ,可以是volatile store -> volatile load ,或者monitor exit -> monitor enter a1->b1->b2->c2顺序连接a1->b1->b2->c2

由于我们需要保证a1->c2的顺序,所以a1不能与b1重新排序,并且c2不能与b2重新排序; 也就是说,蟑螂不能“退房”。

另一方面,JMM希望尽可能地弱; 它没有说明c1a2,b2,c2之间的影响; 因此, c1可以用b1自由重新排序。 类似地, a2可以用b2重新排序。 也就是说,蟑螂可以“登记”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM