java單例類的幾種實現
一,最簡單的方式
public class Singleton{ private Singleton(){}; private static Singleton instance = new Singleton(); public static Singleton getInstance(){ return instance; } }
首先構造函數聲明為private,防止被外部創建該類的實例。聲明一個static的成員變量instance並分配實例,當Singleton類被加載時,instance便會被創建,可以通過靜態方法getInstance方法獲取到該實例。
二,懶加載的單例類
public class Singleton{ private Singleton(){}; private static Singleton instance = null; public static synchronized Singleton getInstance(){ if(instance == null){ instance = new Singleton(); }return instance; } }
因為instance初始值為null,所以在Singleton被加載的時候,並不會實例化,只有調用getInstance方法的時候才會創建實例。為了防止多線程並發調用getInstance方法時instance被多次創建,所以使用synchronized關鍵字進行線程同步。
該實現缺點是,每次調用getInstance方法都要進行線程同步,影響並發量。
三,改進懶加載的單例類
public class Singleton{ private Singleton(){}; private static volatileSingleton instance = null; public static Singleton getInstance(){ if(instance == null){ synchronized(Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; } }
通過雙檢索的方式進行優化,只有在intance沒有值時才進行線程同步,此後會直接返回實例。需要註意的是,在synchronized代碼塊中需要再判斷一次instance是否為null,防止多個線程同時通過了第一個為null的判斷。
還需要註意的是,instance需要volatile修飾,防止指令重排序導致的錯誤。
volatile除了保證線程緩存及時同步到主內存並清理其他線程緩存的值,還有一個作用就是防止指令重排序。 instance = new Singleton() 這行代碼編譯後會拆分成三個指令,可以理解成如下代碼:
1,Singleton temp = malloc(); // 分配內存
2,constructor(temp); // 調用構造函數對分配的內存進行初始化
3,instance = temp; // 初始化完成的內存地址賦值給instance
編譯器為了優化指令,重排序後,可能會變成了下面的代碼:
1,Singleton temp = malloc(); // 分配內存
2,instance = temp; // 初始化完成的內存地址賦值給instance
3,constructor(instance); // 調用構造函數對分配的內存進行初始化
如果代碼執行了上面的第二步,instance已經賦值不為null,但並沒有初始化,這是如果第二個線程調用getInstance方法就會直接獲得instance,調用instance時引發錯誤。volatile可以防止重排序。
四,通過內部類實現單例
public class Singleton{ private static class SingletonHolder{ private static Singleton instance = new Singleton(); } private Singleton(){}; private static volatile Singleton instance = null; public static Singleton getInstance(){ return SingletonHolder.instance; } }
Singleton的實例被靜態內部類SingletonHolder持有,只有在調用getInstance方法時,SingletonHolder才會被加載,instance被實例化。既實現懶加載,也不會有線程安全問題。
java單例類的幾種實現