没有同步顺序的程序

Program without synchronization order

我正在查看我发现的最简单的示例之一,并开始推理 SO(同步顺序)或更准确地说,缺少它。考虑以下示例:

int a, b; // two shared variables 

Thread-X:

 void threadX() {
     synchronized(this) {
         a = 1;
     }
     synchronized(this) {
         b = 1;
     }
 }

还有一个reader线程,Thread-Y:

 void threadY() {
     int r1 = b;
     int r2 = a;
 }

为简单起见,我们假设 Thread-Y 完全按照此顺序进行读取:它肯定会先读取 b,然后再读取 a(与写作相反) .

允许读取线程查看 [1, 0](例如 b=1 发生在 之前 a=1)。我想我也明白为什么:因为两个动作之间有 no synchronization order,因此没有 happens-before 并且根据 JLS 这是数据竞争:

When a program contains two conflicting accesses that are not ordered by a happens-before relationship, it is said to contain a data race.

因此阅读 ab 是两个活泼的阅读,所以看到 b=1a=0 是允许和可能的。

现在这又允许 JVM 在 writer 中进行锁粗化,所以它变成:

 void threadX() {
     synchronized(this) {
         a = 1;
         b = 1;
     }
 }

我的问题是,如果 reader 最初是这样写的:

void threadY() {
     synchronized(this) {
         int r1 = b;
     }

     synchronized(this) {
         int r2 = a;
     }
 }

是否仍允许粗化锁?我认为我知道答案,但我也想听听有根据的解释。

允许锁粗化(和重新排序),因为同步读取器在粗化锁之前或之后得到排序。他们将永远无法看到持有粗化锁时发生了什么,因此无法观察到锁定代码内的任何重新排序。

有关详细信息,请参阅: https://shipilev.net/blog/2016/close-encounters-of-jmm-kind/#myth-barriers-are-sane

顺便问个好问题。前段时间我也在为这个特殊的例子苦苦挣扎:)

如果 JVM 可以证明后续的 synchronized 块将使用相同的对象,那么锁粗化总是可能的。即使是 reader

void threadY() {
     synchronized(this) {
         int r1 = b;
     }

     synchronized(this) {
         int r2 = a;
     }
 }

也可能得到优化

void threadY() {
     synchronized(this) {
         int r1 = b;
         int r2 = a;
     }
 }

但是如果两个方法都是同一个class的实例方法,也就是说,它们的this指的是同一个对象,它们的执行不能重叠。因此,即使读取 and/or 和写入重新排序,结果 [1, 0] 也是不可能的。

甚至锁消除也是允许的,只要执行环境能保证结果[1, 0]永远不会发生即可。一个众所周知的例子是这样一种情况,即该对象从未被其他线程看到。

是的,这是允许的。


这里简单解释一下。

记住 synchronized 块:

  1. 原子执行:
    • 当另一个线程持有相同的锁时,一个线程无法进入 synchronized
    • 当线程进入 synchronized 块时,它会立即看到在先前执行的 synchronized 块中完成的所有操作
  2. 全局顺序执行,与每个线程的程序顺序一致(你说的同步顺序)

换句话说,synchronized 块始终以全局顺序自动执行。 不同的执行在 synchronized 块的交错方式上可能有所不同,但情况总是如此:

  1. 来自 threadX() 的第一个 synchronized 块总是在第二个
  2. 之前执行
  3. 来自 threadY()
  4. synchronized 块相同

有 6 种可能的交错:

threadX          threadY           threadX          threadY             threadX          threadY       
-------------------------------    -------------------------------      -------------------------------
synchronized { |                   synchronized { |                     synchronized { |               
  a = 1;       |                     a = 1;       |                       a = 1;       |               
}              |                   }              |                     }              |               
synchronized { |                                  | synchronized {                     | synchronized {
  b = 1;       |                                  |   int r1 = b;                      |   int r1 = b; 
}              |                                  | }                                  | }             
               | synchronized {    synchronized { |                                    | synchronized {
               |   int r1 = b;       b = 1;       |                                    |   int r2 = a; 
               | }                 }              |                                    | }             
               | synchronized {                   | synchronized {      synchronized { |               
               |   int r2 = a;                    |   int r2 = a;         b = 1        |               
               | }                                | }                   }              | }             
           (Case A)                           (Case B)                             (Case C)            
                                                                                                       
                                                                                                       
threadX          threadY           threadX          threadY             threadX          threadY       
-------------------------------    -------------------------------      -------------------------------
               | synchronized {                   | synchronized {                     | synchronized {
               |   int r1 = b;                    |   int r1 = b;                      |   int r1 = b; 
               | }                                | }                                  | }             
               | synchronized {    synchronized { |                     synchronized { |               
               |   int r2 = a;       a = 1;       |                       a = 1;       |               
               | }                 }              |                     }              |               
synchronized { |                                  | synchronized {      synchronized { |               
  a = 1;       |                                  |   int r2 = a;         b = 1;       |               
}              |                                  | }                   }              |               
synchronized { |                   synchronized { |                                    | synchronized {
  b = 1;       |                     b = 1;       |                                    |   int r2 = a; 
}              |                   }              |                                    | }             
           (Case D)                          (Case E)                             (Case F)             

当您在 threadY() 中合并 synchronized 个块时:

void threadY() {                    void threadY() {          
     synchronized(this) {                synchronized(this) { 
         int r1 = b;                       int r1 = b;        
     }                        =>           int r2 = a;        
     synchronized(this) {                }                    
         int r2 = a;                }                         
     }                                                        
 }                                                            

然后你实际上只保留 threadY() 中的 synchronized 个块彼此相邻的情况:即情况 A、C 和 D。

既然这次优化后没有出现新的可能执行,那么这次优化是合法的。


要获得更严格和详细的解释,我建议:

  1. J. Manson's Ph.D. Thesis on JMM
  2. 中的“锁定粗化”章节
  3. 锁粗化example in an article by A. Shipilev as recommended in