前言:不斷學習就是程序員的宿命
一、單例模式
所謂的單例設計模式,就是采取一定的方法保證整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只能提供一個取得對象實例的方法(靜態方法)。比如Hibernate的SessionFactory,它充當數據存儲源的代理,並負責創建session對象。SessionFactory並不是輕量級的
單例模式有以下8種方式:
二、餓漢式(靜態常量)

public class Singleton01 { //本類內部創建對象實例 private final static Singleton01 instance=new Singleton01(); //構造器私有化,外部不能new private Singleton01(){} //提供一個公有靜態方法,返回實例對象 private static Singleton01 getInstance(){ return instance; } public static void main(String[] args) { //測試 Singleton01 instance1 = Singleton01.getInstance(); Singleton01 instance2 = Singleton01.getInstance(); System.out.println(instance1==instance2); System.out.println(instance1.hashCode()); System.out.println(instance1.hashCode()); } }
分析:
(1)優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
(2)缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費
三、餓漢式(靜態代碼塊)

public class Singleton02 { //本類內部創建對象實例 private static Singleton02 instance; //構造器私有化, 外部不能new private Singleton02(){} // 在靜態代碼塊中,創建單例對象 static { instance = new Singleton02(); } //提供一個公有的靜態方法,返回實例對象 public static Singleton02 getInstance() { return instance; } public static void main(String[] args) { //測試 Singleton02 instance = Singleton02.getInstance(); Singleton02 instance2 = Singleton02.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)這種方式和上述方式類似,只不過將類實例化的過程放在了靜態代碼塊中,也就是在裝載的時候執行靜態代碼中的代碼,初始化類的實例
(2)結論:單例方式可用,但可能會造成內存浪費
四、懶漢式(線程不安全)

/** * @ClassName: Singleton03 * @Description: 懶漢式-線程不安全 * @Author: xiedong * @Date: 2020/4/5 0:19 */ public class Singleton03 { private static Singleton03 instance; private Singleton03() {} //提供一個靜態的公有方法,當使用到該方法時,才去創建 instance public static Singleton03 getInstance() { if(instance == null) { instance = new Singleton03(); } return instance; } public static void main(String[] args) { System.out.println("懶漢式1 , 線程不安全~"); Singleton03 instance = Singleton03.getInstance(); Singleton03 instance2 = Singleton03.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)起到了Lazy Loading的效果,但是只能在單線程下使用
(2)如果在多線程下,一個線程進入if(instance==null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例,所以在多線程環境下不可使用這種方式。
(3)結論:實際開發中,不要使用這種方式
五、懶漢式(同步方法保證線程安全)

/** * @ClassName: Singleton04 * @Description: 懶漢式-同步方法 * @Author: xiedong * @Date: 2020/4/5 0:53 */ public class Singleton04 { private static Singleton04 instance; private Singleton04() {} //提供一個靜態的公有方法,加入同步處理的代碼,解決線程安全問題 //即懶漢式 public static synchronized Singleton04 getInstance() { if(instance == null) { instance = new Singleton04(); } return instance; } public static void main(String[] args) { System.out.println("懶漢式2 , 線程安全~"); Singleton04 instance = Singleton04.getInstance(); Singleton04 instance2 = Singleton04.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)解決線程安全問題
(2)效率太低了,每個線程在想獲得類的實例的時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,后面的想要獲得該類實例,直接return就行了,方法同步效率太低了。
(3)結論:在實際開發中,不推薦使用這種方式
六、懶漢式(同步代碼塊保證線程安全)

/** * @ClassName: Singleton05 * @Description: 懶漢式-同步代碼塊 * @Author: xiedong * @Date: 2020/4/5 0:56 */ public class Singleton05 { private static Singleton05 instance; private Singleton05() { } public static Singleton05 getInstance() { if (instance == null) { synchronized (Singleton05.class) { instance = new Singleton05(); } } return instance; } public static void main(String[] args) { Singleton05 instance = Singleton05.getInstance(); Singleton05 instance2 = Singleton05.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)這種方式,本意是想對上述(同步方法)實現方式的改進,因為前面同步方法效率太低了,改為同步產生實例化的代碼塊
(2)但是這種同步並不能起到線程同步的作用。假如一個線程進入了if(instance==null)判斷語句塊,還未來得及往下執行,另一個線程也通過了這個判斷語句,這時便會產生多個實例。
(3)結論:實際開發中,不能使用這種方式
七、雙重檢查

/** * @ClassName: Singleton06 * @Description: 雙重檢查 * @Author: xiedong * @Date: 2020/4/5 1:07 */ public class Singleton06 { private static volatile Singleton06 instance; private Singleton06() {} //提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題, 同時解決懶加載問題 //同時保證了效率, 推薦使用 public static synchronized Singleton06 getInstance() { if(instance == null) { synchronized (Singleton06.class) { if(instance == null) { instance = new Singleton06(); } } } return instance; } public static void main(String[] args) { System.out.println("雙重檢查"); Singleton06 instance = Singleton06.getInstance(); Singleton06 instance2 = Singleton06.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)Double-Check概念是多線程開發中常使用到的,如代碼中所示,兩次if(singleton==null)檢查,這樣就保證線程安全了。
(2)這樣實例化代碼只用執行一次,后面再次訪問到時,判斷if(singleton==null)直接return實例對象,也避免了反復進行方法同步。
(3)線程安全;延遲加載;效率較高
(4)結論:在實際開發中,推薦使用這種單例設計模式
八、靜態內部類

/** * @ClassName: Singleton7 * @Description: * @Author: xiedong * @Date: 2020/4/5 1:20 */ public class Singleton7 { private static volatile Singleton7 instance; //構造器私有化 private Singleton7() {} //寫一個靜態內部類,該類中有一個靜態屬性 Singleton private static class SingletonInstance { private static final Singleton7 INSTANCE = new Singleton7(); } //提供一個靜態的公有方法,直接返回SingletonInstance.INSTANCE public static synchronized Singleton7 getInstance() { return SingletonInstance.INSTANCE; } public static void main(String[] args) { System.out.println("使用靜態內部類完成單例模式"); Singleton7 instance = Singleton7.getInstance(); Singleton7 instance2 = Singleton7.getInstance(); System.out.println(instance == instance2); // true System.out.println("instance.hashCode=" + instance.hashCode()); System.out.println("instance2.hashCode=" + instance2.hashCode()); } }
分析:
(1)這種方式采用了類裝載的機制來保證初始化實例時只有一個線程
(2)靜態內部類方式在Singleton07類(外部類)被裝載時並不會立即實例化,而是在需要實例化時,調用getInstance()方法,才會裝載SingletonInstance類,從而完成Singleton的實例化
(3)類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。
(4)優點:避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高
(5)結論:推薦使用
九、枚舉

/** * @ClassName: Singleton08 * @Description: * @Author: xiedong * @Date: 2020/4/5 1:30 */ public class Singleton08 { public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); instance.sayOK(); } } //使用枚舉,可以實現單例, 推薦 enum Singleton { INSTANCE; //屬性 public void sayOK() { System.out.println("ok~"); } }
分析:
(1)這里借助JDK1.5中添加的枚舉來實現單例模式。不僅能夠避免多線程同步問題,而且還能防止反序列化重新創建新的對象
(2)這種方式是Effective Java作者Josh Bloch提倡的方式
(3)結論:推薦使用
十、單例模式在JDK應用舉例
可以發現,JDK中使用餓漢式-靜態屬性
十一、總結
(1)單例模式保證了系統內存中該類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷毀的對象,使用單例模式可以提高系統性能
(2)當想實例化一個單例類的時候,必須要記住使用相應獲取對象的方法,而不是new
(3)單例模式的使用場景:需要頻繁的創建和銷毀對象、創建對象時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)