單例模式(Singleton Pattern)
是 Java 中最簡單的設計模式之一,這種類型的設計模式屬于創建型模式。目的是確保一個類只有一個實例,并提供一個全局訪問點來獲取這個實例。這樣做可以節省系統資源,并且保證某些類在系統中只存在一個實例。
主要解決:一個全局使用的類頻繁地創建與銷毀。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。
關鍵代碼:構造函數是私有的(private關鍵字)
缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心外面怎么樣來實例化。
注意:
1、單例類只能有一個實例。
2、單例類必須自己創建自己的唯一實例。
3、單例類必須給所有其他對象提供這一實例。
類型:單例模式可以分為幾種類型,包括 餓漢式,懶漢式,登記式 。餓漢式單例在類加載時就創建實例,而懶漢式單例則在首次使用時創建實例。這兩種實現方式都需要考慮線程安全問題。
多線程情況下: 雙重檢查鎖定(double-checked locking)是一種常用的實現線程安全單例的方法。
======================================================== 以上八股文 來源 網上亂查的 ================================================================================================
舉個簡單小例子: 在使用數據庫時, 首先要獲取 jdbc 鏈接, 然后進行增刪改查操作, 每次 增加操作 ,刪除操作 ,查詢 和修改操作時, 都要獲取 jdbc 鏈接。
那么這個時候, 只保存一個 jdbc鏈接 在系統中, 每次操作數據庫時 使用創建好 的JDBC 鏈接 就不需要每次操作 都創建了
代碼:
/** * 餓漢式 */ public class SingletonJDBC { private static SingletonJDBC instance = new SingletonJDBC(); private SingletonJDBC(){} public static SingletonJDBC getInstance(){ return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
public static void main(String[] args) {
//SingletonJDBC jdbc = new SingletonJDBC();
SingletonJDBC singletonJDBC = SingletonJDBC.getInstance();
singletonJDBC.JdbcMessage();
}
標紅地方會報如下錯誤,private

以上代碼 使用了static 關鍵字(static 當Java虛擬機(JVM)加載類時,就會執行該代碼塊), 加載的時候就將 jdbc 給 創建好了, 可是, 在這個時候不操作數據庫,那么就不應該 加載, 在使用的時候再加載(懶漢式出現 lazy loading)
代碼:
/** * 懶漢漢式 lazy loading */ public class SingletonJDBC { private static SingletonJDBC instance; private SingletonJDBC(){} public static SingletonJDBC getInstance(){ if (null == instance){ instance = new SingletonJDBC(); } return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
出現問題了, 在使用數據庫的時候,多個地方 同時(多線程) 需要jdbc 鏈接 ,那么當第一個 使用者 來的時候, 走 到了 null == instance 的時候, 第二個使用者來了, 也是空, 那么 它們創建的 就不是一個 相同的 SingletonJDBC 了 !!
驗證一下
private static SingletonJDBC instance; private SingletonJDBC(){} public static SingletonJDBC getInstance(){ if (null == instance){ try { Thread.sleep(10); }catch (Exception e){ e.printStackTrace(); } instance = new SingletonJDBC(); } return instance; } public void JdbcMessage(){ System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } } public static void main(String[] args) { for (int i = 0; i < 1000; i++) { new Thread(() -> { SingletonJDBC singletonJDBC = SingletonJDBC.getInstance(); singletonJDBC.JdbcMessage(); System.out.println(singletonJDBC.hashCode()); }).start();
}
}
結果:
這些 hashCode 不對, 不是一個 (又增加了多余的開銷,如果是業務中的唯一數據, 這不就出問題了么)

修改一下 加個 synchronized,代碼如下
public static synchronized SingletonJDBC getInstance(){ if (null == instance){ try { Thread.sleep(10); }catch (Exception e){ e.printStackTrace(); } instance = new SingletonJDBC(); } return instance; }
結果:完美解決問題 (但是, 加了synchronized 是鎖住了 SingletonJDBC 整個 對象, 每次過來 要判斷 鎖 的情況, 效率又低了【如果是上千萬數據交換】)

修改一下 (雙重檢查),代碼如下 ( volatile 關鍵字 )
public class SingletonJDBC {
private static volatile SingletonJDBC instance;
private SingletonJDBC() {
}
public static SingletonJDBC getInstance() {
if (null == instance) {
synchronized (SingletonJDBC.class) {
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
if (null == instance) {
instance = new SingletonJDBC();
}
}
}
return instance;
}
public void JdbcMessage() {
System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX ");
}
}
代碼 復雜,效果不是很理想,修改一下, (登記式/靜態內部類)
/** * 登記式/靜態內部類 */ public class SingletonJDBC { private SingletonJDBC() { } private static class SingletonJDBCHolder { private static final SingletonJDBC INSTANCE = new SingletonJDBC(); } public static SingletonJDBC getInstance() { return SingletonJDBCHolder.INSTANCE; } public void JdbcMessage() { System.out.println(" JDBC://XXXXXXXXXXXXXXXXXXXX "); } }
說明: 在類加載 時候, 是不會 加載靜態內部類的 , 只有當調用 getInstance 方法時候,會顯式裝載 SingletonJDBCHolder 。這種方式同樣利用了 classloader 機制來保證初始化 instance 時只有一個線程。
結合上篇文章 , (工廠模式 + 單例模式) 可以簡單的設計一個 數據庫鏈接復用代碼
=============================================================== 收 工====================================================================
補充 以下內容來源 ==============================菜鳥教程=================================
枚舉
是否 Lazy 初始化:否
是否多線程安全:是
實現難度:易
描述:這種實現方式還沒有被廣泛采用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化。
這種方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化。不過,由于 JDK1.5 之后才加入 enum 特性,用這種方式寫不免讓人感覺生疏,在實際工作中,也很少用。
不能通過 reflection attack 來調用私有構造方法。
實例
public enum Singleton { INSTANCE; public void whateverMethod() { } }
經驗之談:一般情況下,不建議使用 懶漢方式,建議使用 (登記式/靜態內部類),如果涉及到反序列化創建對象時,可以嘗試使用枚舉方式。如果有其他特殊的需求,可以考慮使用 雙檢鎖方式。
浙公網安備 33010602011771號