前言
單例模式作爲開發過程中最常用的設計模式之一,是程序員必須瞭解和掌握的技能,但是單例的實現方式和手段有很多種,每一種都有着各自的優缺點,如何在代碼中運用就需要我們對每一種實現方式都瞭如執掌方可運籌帷幄。
單例的實現方式
靜態常量餓漢式
- 代碼
public class Single { private static Single single = new Single(); // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } public static Single getInstance() { return single; } }
- 優點
- 線程安全;
- 實現起來容易,代碼容易理解;
- 沒有鎖存在,執行性能高;
- 缺點
- 沒有實現懶加載,可能產生垃圾對象,如果能確定這個對象必定會使用,可以考慮這種方式
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
靜態代碼塊餓漢式
- 代碼
public class Single { private static Single single = null; static{ single = new Single(); } // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } public static Single getInstance() { return single; } }
- 優點
- 線程安全;
- 實現起來容易,代碼容易理解;
- 沒有鎖存在,執行性能高;
- 缺點
- 沒有實現懶加載,可能產生垃圾對象,如果能確定這個對象必定會使用,可以考慮這種方式
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
懶漢式一(不要使用)
- 代碼
public class Single { private static Single single = null; // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } private static Single getInstance() { // 當多個線程同時執行帶這裏的時候,會創建出多個對象 if (null == single) { single = new Single(); } return single; } }
- 優點
- 實現了懶加載;
- 實現起來容易,代碼容易理解;
- 缺點
- 非線程安全,無法保證單例,所以,不要使用。
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
懶漢式二(不推薦使用)
- 代碼
public class Single { private static Single single = null; // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } private static synchronized Single getInstance() { if (null == single) { single = new Single(); } return single; } }
- 優點
- 實現了懶加載;
- 線程安全
- 實現起來容易,代碼容易理解;
- 缺點
- 鎖粒度太粗,導致執行的性能會下降
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
懶漢式三(不要使用)
- 代碼
public class Single { private static Single single = null; // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } private static Single getInstance() { if (null == single) { // 當多個線程通知執行到這裏來了之後,無法保證對象單一 synchronized(Single.class){ single = new Single(); } } return single; } }
- 優點
- 實現了懶加載;
- 實現難度一般,代碼容易理解;
- 缺點
- 線程不安全,無法保證對象單一,所以,不要使用。
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
雙重檢查DCL(推薦使用)
- 代碼
public class Single { //這裏一定要加volatile 否則可能因爲指令重排的問題導致對象未初始化完成的情況 private volatile static Single single = null; // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } public static Single getInstance() { if (null == single) { synchronized (Single.class) { if (null == single) { single4 = new Single(); } } } return single; } }
- 優點
- 實現了懶加載;
- 線程安全
- 鎖粒度較細,只有第一次初始化的時候會走到synchronized部分
- 缺點
- 實現起來相對複雜,對於volatile的理解會比較的難
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
關於爲什麼要加volatile,可點擊參考:剖析synchronized、volatile的實現細節,裏面有詳細的說明!
- 優點
靜態內部類(推薦使用)
- 代碼(內部類的方式)
public class Single { // 一定要將默認構造方法設置爲private 否則反射實例化將破壞單例 private Single() { } private static class LazyHolder { private static final Single INSTANCE = new Single(); } public static Single getInstance() { return LazyHolder.INSTANCE; } }
- 優點
- 實現了懶加載;
- 線程安全
- 沒有鎖,性能較好
- 缺點
- 實現及理解起來相對複雜
- 存在構造方式未設置private而導致反射實例化破壞單例的風險
- 優點
枚舉單例(推薦使用)
- 代碼
public enum Single6 { INSTANCE; public void test() { System.out.println("test()"); } }
- 優點
- 線程安全;
- 沒有鎖,性能較好;
- 實現簡單,理解容易;
- 不會因構造方法未設置爲private而帶來的破壞單例的風險.
- 缺點
- 未實現了懶加載;
- 優點
總結
以上列舉了開發過程中所有的單例實現方式,同時還列出了詳細的其詳細的優缺點;對於枚舉方式、DCL方式以及靜態內部類的方式是個人相對比較推薦的方式;可以視情況使用;至於餓漢式的兩種寫法,由於比較簡單,如果對應的單例對象在系統中必定用到而且頻繁使用,也可以考慮使用;所以,沒有更好,只有最好!合適纔好!!!