在計算機執行指令的順序在通過程序編譯器編譯以後造成的指令序列,通常而言,這個指令序列是會輸出肯定的結果;以確保每一次的執行都有肯定的結果。可是,通常狀況下,CPU和編譯器爲了提高程序執行的效率,會按照必定的規則容許進行指令優化,在某些狀況下,這種優化會帶來一些執行的邏輯問題,主要的緣由是代碼邏輯之間是存在必定的前後順序,在併發執行狀況下,會發生二義性,即按照不一樣的執行邏輯,會獲得不一樣的結果信息。java
代碼例子:編程
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並無初始化完,就會引起空指針異常。app
Happens-Before 約束了編譯器的優化行爲,雖容許編譯器優化,可是要求編譯器優化後必定遵照Happens-Before 規則。函數
1.程序的順序性規則
在一個線程中,按照程序順序,前面的操做Happens-Before於後續的任意操做。程序前面對某個變量的修改必定是對後續操做可見的。post
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/