java 單例模式

zy平平仄仄發表於2024-04-14

單例模式(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() {  
    }  
}

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

相關文章