1. 程式人生 > 實用技巧 >設計模式系列(一)單例模式

設計模式系列(一)單例模式

1.單例模式

這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。這種模式涉及到一個單一的類 ,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式, 可以直接訪問,不需要例項化該類的物件。

  • 單例類只能有一個例項。
  • 單例類必須自己建立自己的唯一例項。
  • 單例類必須給所有其他物件提供這一例項。

意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
主要解決:一個全域性使用的類頻繁地建立與銷燬。
何時使用:當您想控制例項數目,節省系統資源的時候。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。
關鍵程式碼:建構函式是私有的。

優點
  1. 在記憶體裡只有一個例項,減少了記憶體的開銷,尤其是頻繁的建立和銷燬例項(比如管理學院首頁頁面快取)。
  2. 避免對資源的多重佔用(比如寫檔案操作)。
缺點

沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

使用場景:
  1. 要求生產唯一序列號。
  2. WEB 中的計數器,不用每次重新整理都在資料庫里加一次,用單例先快取起來。
  3. 建立的一個物件需要消耗的資源過多,比如 I/O 與資料庫的連線等。

注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class)
防止多執行緒同時進入造成 instance 被多次例項化。

  • 餓漢式 靜態常量
//餓漢式(靜態常亮)在類裝載的時候完成了例項化,避免了執行緒同步問題。但是沒有達到懶載入,有可能造成記憶體的浪費。
class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private final static Singleton instance = new Singleton();

    //獲取例項
    public static Singleton getInstance() {
        return instance;
    }
}
  • 餓漢式 靜態程式碼塊
//優缺點同上
class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    //獲取例項
    public static Singleton getInstance() {
        return instance;
    }
}
  • 懶漢式 執行緒不安全

class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private static Singleton instance;

    //獲取例項
    public static Singleton getInstance() {
        //在使用的時候才去建立(此處會出現執行緒不安全問題)
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}
  • 懶漢式 執行緒安全 同步方法

class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private static Singleton instance;

    //獲取例項    synchronized讓執行緒依次獲取instance
    public static synchronized Singleton getInstance() {
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}
  • 懶漢式 執行緒安全 同步方法
//雖然可以解決執行緒不安全問題,但是每次獲取instance都走同步方法,會導致效率低
class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private static Singleton instance;

    //獲取例項    synchronized讓執行緒依次獲取instance
    public static synchronized Singleton getInstance() {
        if (instance == null)
            instance = new Singleton();
        return instance;
    }
}
  • 懶漢式 執行緒安全 同步程式碼塊(雙重判斷) volatile修飾變數
//volatile 共享變數 可以在修改後立刻重新整理
class Singleton {
    //構造私有化
    private Singleton() {

    }

    //內部建立物件例項
    private static volatile Singleton instance;

    //獲取例項    synchronized讓執行緒依次獲取instance
    public static synchronized Singleton getInstance() {
        if (instance == null)
            //沒有作用 因為已經進入if語句了
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        return instance;
    }
}
  • 靜態內部類 (外部類被裝載的時候,內部類不會被裝載)

class Singleton {
    //構造私有化
    private Singleton() {

    }

    //靜態內部類,該類中有一個靜態的屬性,靜態內部類只有被呼叫的時候,才回裝載(此處相當於懶載入,可以節省記憶體) 在類裝載的時候,是執行緒安全的。 而且類只裝載一次
    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }

    //獲取例項    synchronized讓執行緒依次獲取instance
    public static synchronized Singleton getInstance() {
        return SingletonInstance.instance;
    }
}

-列舉方式 避免多執行緒同步問題,防止反序列化重新建立物件

enum Singleton {
    INSTANCE;

    public void method() {
        System.out.println("單例呼叫");
    }
}
單例模式在jdk中的應用

Runtime物件,用到了餓漢式的單例

如何破壞單例,如何防止
  • 通過反射破壞單例
    雖然構造方法以及被私有化,但還是可以通過反射產生新的物件。

          //通過反射獲取
          Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
          constructor.setAccessible(true);
          Singleton reflex = constructor.newInstance();
          System.out.println("reflex的hashCode:"+reflex.hashCode());
    

    如何防止反射破壞
    建立一個全域性變數設為true,在構造方法中判斷,如果是一個次建立,就把變數設為false,如果不是,就丟擲異常。

      class Singleton {
        private static boolean isFristCreate = true;//預設是第一次建立
      
        //構造私有化
        private Singleton() {
          if (isFristCreate) {
            synchronized (Singleton.class) {
              if (isFristCreate) {
                isFristCreate = false;
              }
            }
          } else {
            throw new RuntimeException("已然被例項化一次,不能在例項化");
          }
        }
      
        //靜態內部類,該類中有一個靜態的屬性,靜態內部類只有被呼叫的時候,才回裝載(此處相當於懶載入,可以節省記憶體) 在類裝載的時候,是執行緒安全的。 而且類只裝載一次
        private static class SingletonInstance {
          private static final Singleton instance = new Singleton();
        }
      
        //獲取例項    synchronized讓執行緒依次獲取instance
        public static synchronized Singleton getInstance() {
          return SingletonInstance.instance;
        }
      }
    
  • 通過克隆方式破壞

  • 通過反序列化方式破壞