Java併發程式設計(四)有序性
一、指令重排
在計算機執行指令的順序在經過程式編譯器編譯之後形成的指令序列,一般而言,這個指令序列是會輸出確定的結果;以確保每一次的執行都有確定的結果。但是,一般情況下,CPU和編譯器為了提升程式執行的效率,會按照一定的規則允許進行指令優化,在某些情況下,這種優化會帶來一些執行的邏輯問題,主要的原因是程式碼邏輯之間是存在一定的先後順序,在併發執行情況下,會發生二義性,即按照不同的執行邏輯,會得到不同的結果資訊。
程式碼例子:
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
複製程式碼
對於如下程式碼:
singleton = new Singleton();
複製程式碼
我們以為的順序是:
1.分配一塊記憶體
2.在記憶體上初始化Singleton物件
3.然後M的地址賦值給instance變數
但實際上不是,檢視JAVA位元組碼:
public class com.javashizhan.concurrent.demo.base.Singleton {
public static com.javashizhan.concurrent.demo.base.Singleton getInstance();
Code:
0: aconst_null
1: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
4: if_acmpne 39
7: ldc #3 // class com/javashizhan/concurrent/demo/base/Singleton
9: dup
10: astore_0
11: monitorenter
12: aconst_null
13: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
16: if_acmpne 29
19: new #3 // class com/javashizhan/concurrent/demo/base/Singleton
22: dup
23: invokespecial #4 // Method "<init>":()V
26: putstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
29: aload_0
30: monitorexit
31: goto 39
34: astore_1
35: aload_0
36: monitorexit
37: aload_1
38: athrow
39: getstatic #2 // Field singleton:Lcom/javashizhan/concurrent/demo/base/Singleton;
42: areturn
Exception table:
from to target type
12 31 34 any
34 37 34 any
}
複製程式碼
看19~26,實際的順序是:
1.分配一塊記憶體
2.將M的地址賦值給instance變數
3.最後在記憶體M上初始化Singleton物件。
由於指令的順序問題,多執行緒併發時執行緒A執行到26之前發生了執行緒切換,此時執行緒B發現null == singleton不成立,獲取到singleton,而此時singleton並沒有初始化完,就會引發空指標異常。
二、Happens-Before 規則
Happens-Before 約束了編譯器的優化行為,雖允許編譯器優化,但是要求編譯器優化後一定遵守Happens-Before 規則。
1.程式的順序性規則
在一個執行緒中,按照程式順序,前面的操作Happens-Before於後續的任意操作。程式前面對某個變數的修改一定是對後續操作可見的。
2.volatile變數規則 對一個volatile變數的寫操作,Happens-Before於後續對這個volatile變數的讀操作。
3.傳遞性 如果A Happens-Before於B,B Happens-Before於C,那麼A Happens-Before於C。
4.鎖的規則
對一個鎖的解鎖Happens-Before於對這個鎖的加鎖。
5.執行緒Start規則 主執行緒A啟動子執行緒B後,子執行緒B能看到主執行緒在啟動子執行緒B之前的操作。
6.執行緒join規則
主執行緒A等待子執行緒B完成(主執行緒通過呼叫子執行緒B的join方法實現),當子執行緒B完成後(主執行緒A中join()方法返回),主執行緒能夠看到子執行緒的操作。
7.執行緒中斷規則
對執行緒interrupt()方法的呼叫先行發生於被中斷執行緒的程式碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷髮生。
8.物件終結規則
一個物件的初始化完成(建構函式執行結束)先行發生於它的finalize()方法的開始。
end.
相關閱讀:
Java併發程式設計(一)知識地圖
Java併發程式設計(二)原子性
Java併發程式設計(三)可見性
Java併發程式設計(五)建立執行緒方式概覽
Java併發程式設計入門(六)synchronized用法
Java併發程式設計入門(七)輕鬆理解wait和notify以及使用場景
Java併發程式設計入門(八)執行緒生命週期
Java併發程式設計入門(九)死鎖和死鎖定位
Java併發程式設計入門(十)鎖優化
Java併發程式設計入門(十一)限流場景和Spring限流器實現
Java併發程式設計入門(十二)生產者和消費者模式-程式碼模板
Java併發程式設計入門(十三)讀寫鎖和快取模板
Java併發程式設計入門(十四)CountDownLatch應用場景
Java併發程式設計入門(十五)CyclicBarrier應用場景
Java併發程式設計入門(十六)秒懂執行緒池差別
Java併發程式設計入門(十七)一圖掌握執行緒常用類和介面
Java併發程式設計入門(十八)再論執行緒安全
Java極客站點: javageektour.com/