單例模式1.0:java
public class Singleton { private static Singleton sInstance; public static Singleton getInstance() { if (sInstance == null) { // 1 sInstance = new Singleton(); } return sInstance; } private Singleton() { } }
這種方式很辣雞,由於多線程環境下不能保證單例。多線程
單例模式2.0:併發
public class Singleton { private static volatile Singleton sInstance; public static synchronized Singleton getInstance() { if (sInstance == null) { sInstance = new Singleton(); } return sInstance; } private Singleton() { } }
這種方式也很辣雞,由於多線程環境下每一個線程執行getInstance()都要阻塞,效率很低。優化
單例模式3.0:spa
public class Singleton { private static Singleton sInstance; public static Singleton getInstance() { if (sInstance == null) { // 位置1 synchronized (Singleton.class) { if (sInstance == null) { sInstance = new Singleton(); // 位置2 } } } return sInstance; } private Singleton() {} }
這種方式使用雙重檢查鎖,多線程環境下執行getInstance()時先判斷單例對象是否已經初始化,若是已經初始化,就直接返回單例對象,若是未初始化,就在同步代碼塊中先進行初始化,而後返回,效率很高。線程
可是這種方式是一個錯誤的優化,問題的根源出在位置2對象
sInstance =new Singleton();這句話建立了一個對象,他能夠分解成爲以下3行代碼:blog
memory = allocate(); // 1.分配對象的內存空間 ctorInstance(memory); // 2.初始化對象 sInstance = memory; // 3.設置sInstance指向剛分配的內存地址
上述僞代碼中的2和3之間可能會發生重排序,重排序後的執行順序以下排序
memory = allocate(); // 1.分配對象的內存空間 sInstance = memory; // 2.設置sInstance指向剛分配的內存地址,此時對象尚未被初始化 ctorInstance(memory); // 3.初始化對象
由於這種重排序並不影響Java規範中的規範:intra-thread sematics容許那些在單線程內不會改變單線程程序執行結果的重排序。內存
可是多線程併發時可能會出現如下狀況
線程B訪問到的是一個還未初始化的對象。
解決方案1:
public class Singleton { private static volatile Singleton sInstance; public static Singleton getInstance() { if (sInstance == null) { synchronized (Singleton.class) { if (sInstance == null) { sInstance = new Singleton(); } } } return sInstance; } private Singleton() {} }
將對象聲明爲volatitle後,前面的重排序在多線程環境中將會被禁止
解決方案2:
public class Singleton { private Singleton(){}; private static class Inner{ private static Singleton SINGLETION=new Singleton(); } public static Singleton getInstance(){ return Inner.SINGLETION; } }
靜態內部類不會隨着外部類的初始化而初始化,他是要單獨去加載和初始化的,當第一次執行getInstance方法時,Inner類會被初始化。
靜態對象SINGLETION的初始化在Inner類初始化階段進行,類初始化階段即虛擬機執行類構造器<clinit>()方法的過程。
虛擬機會保證一個類的<clinit>()方法在多線程環境下被正確的加鎖和同步,若是多個線程同時初始化一個類,只會有一個線程執行這個類的<clinit>()方法,其它線程都會阻塞等待。