无同步顺序的程序

Eug*_*ene 2 java multithreading jit jvm locking

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

int a, b; // two shared variables 

Thread-X:

 void threadX() {
     synchronized(this) {
         a = 1;
     }
     synchronized(this) {
         b = 1;
     }
 }
Run Code Online (Sandbox Code Playgroud)

还有一个读者线程Thread-Y

 void threadY() {
     int r1 = b;
     int r2 = a;
 }
Run Code Online (Sandbox Code Playgroud)

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

允许读取线程查看[1, 0](就像之前b=1发生的那样)。我想我也理解为什么:因为两个操作之间没有同步顺序,因此没有发生之前,并且根据这是一个数据竞争 a=1JLS

当程序包含两个不按先发生关系排序的冲突访问时,我们称其包含数据争用。

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

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

 void threadX() {
     synchronized(this) {
         a = 1;
         b = 1;
     }
 }
Run Code Online (Sandbox Code Playgroud)

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

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

     synchronized(this) {
         int r2 = a;
     }
 }
Run Code Online (Sandbox Code Playgroud)

仍然允许锁粗化吗?我我知道答案,但我也想听到一个有根据的解释。

小智 7

是的,这是允许的。


这是一个简单的解释。

请记住synchronized块:

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

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

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

有 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)             
Run Code Online (Sandbox Code Playgroud)

当您合并synchronized以下块时threadY()

void threadY() {                    void threadY() {          
     synchronized(this) {                synchronized(this) { 
         int r1 = b;                       int r1 = b;        
     }                        =>           int r2 = a;        
     synchronized(this) {                }                    
         int r2 = a;                }                         
     }                                                        
 }                                                            
Run Code Online (Sandbox Code Playgroud)

然后,您实际上只保留synchronizedthreadY()彼此相邻的允许情况:即情况 A、C 和 D。

由于此优化后没有出现新的可能执行,因此此优化是合法的。


对于更严格和详细的解释,我建议:

  1. J. Manson 博士论文中的“锁定粗化”章节 JMM论文
  2. 按照上面答案中的建议,锁定A. Shipilev 的文章中的粗化示例