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

时间:2016-02-14 07:33:27

标签: java multithreading

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

  1. 同步块内的操作(​​或只是调用它 关键部分)允许重新排序,只要它确认为as-if-serial语义。
  2. 运营(包括两者         “读取和写入”禁止被移动(重新排序)到关键部分之外。
  3. 但是,对于同步块之前或之后的那些操作,它们是否可以移动到临界区?对于这个问题,我发现有些相反。例如,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)都可以 NOT 在MB3和MB4之前向上移动,因为存在一个LoadStore(MB3),后面跟着StoreStore (MB4)。这就是说MonitorExit之后的商店无法进入关键部分。但是,可以在MB4之后向上移动,即括号区域。

    理解2:在MonitorEnter之前的任何加载(在此加载a和加载b)可以在MB2和MB1之后 NOT 向下移动,因为存在LoadLoad(MB1)后跟LoadLoad (MB2)。那就是在MonitorEnter无法移动到关键任务之前的负载。但是,可以在MB2之后向下移动,即括号区域。

    理解3:MonitorExit 之后的任何加载(在此处加载c和加载d)可以在MonitorExit之前向上移动,包括临界区和括号区,但不能超过MonitorEnter。

    理解4:在MonitorEnter 之前的任何商店(此处为商店1和商店2)可以在MonitorEnter之后向下移动,包括关键部分和括号区域,但不能超过MonitorExit。

    然而,所有上述理解或主张都与Jeremy Manson在blog中所说的相反,他声称在下面的代码中提到:

    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”无法在临界区内向上移动, 所以我很困惑,哪一个是正确和完整的?

1 个答案:

答案 0 :(得分:0)

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

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

例如,给定原始指令序列

volatile store x
normal   store y

两条指令之间不需要内存屏障。

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

normal   store y
volatile store x

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

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

罗奇汽车旅馆模型 -

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

thread 1             thread 2

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

建立了同步订单b1->b2,可以是volatile store -> volatile loadmonitor exit -> monitor enter。这会在发生前的顺序中连接a1->b1->b2->c2

由于我们需要保证a1->c2的排序,a1不得与b1重新排序,c2不得与b2重新排序;也就是说,蟑螂不能&#34;退房&#34;。

另一方面,JMM希望尽可能地弱;它没有说明c1a2,b2,c2之间的影响;因此,c1可以b1自由重新排序。同样,a2可以使用b2进行重新排序。也就是说,蟑螂可以检查&#34;。