用兩個例子來講明:java
public class Singleton { private static Singleton uniqueSingleton; private Singleton() {} public static Singleton getInstance() { if (null == uniqueSingleton) {//先判斷是否爲空,沒必要每次都加鎖 synchronized (Singleton.class) {//再獲取鎖 if (null == uniqueSingleton) {//再次判斷是否爲空,由於第二個線程獲取到鎖後,繼續執行,可是第一個線程已經初始化完成了 uniqueSingleton = new Singleton();//初始化對象 } } } return uniqueSingleton; } }
上述代碼是一個雙重鎖的單例模式實現方式,但存在隱患
咱們須要瞭解初始化對象的過程,包含的指令大體以下:
1.分配內存空間
2.初始化對象
3.將對象指向分配的內存空間
有些編譯器爲了提高效率,會將2 3的順序倒置(重排序,先將對象指向分配的內存空間再初始化),所以可能發生讀取到未初始化完成的對象
如何解決?
使用volatile
關鍵字,禁止重排序編程
private volatile static Singleton uniqueSingleton;
public class NoVisibility{ private static boolean ready; private static int number; private static class ReaderThrad extends Thread{ public void run(){ while(!ready){ Thread.yield(); } System.out.println(number); } } public static void main(String [] args){ new ReaderThread().start(); number = 42; ready = true; } }
上述代碼源自<<Java併發編程實踐>>
書中
可能發生的現象:多線程
Thread.yield()
的意思是將當前狀態轉換爲就緒狀態,ReaderThread立刻又獲取到了執行機會,可能致使main線程永遠獲取不到執行的機會number=42
ready=true
不知足Happens-Before
原則,所以JVM能夠對這兩個操做任意的重排序,便可能先執行ready=true
再執行number=42
結語:本篇幅較短,不能深刻的將多線程中有關重排序的問題的講得透徹,若從發散思惟的角度來看來此問題,涉及到的相關知識點很是多,想深刻了解建議閱讀<<Java併發編程實踐>>
<<深刻理解Java虛擬機>>
併發