單例模式(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() { } }
經驗之談:一般情況下,不建議使用 懶漢方式,建議使用 (登記式/靜態內部類),如果涉及到反序列化建立物件時,可以嘗試使用列舉方式。如果有其他特殊的需求,可以考慮使用 雙檢鎖方式。