【常用設計模式】單例設計模式

定義
  單例是一種設計模式,單例模式可以保證系統中只有一個類只有一個實例,而且該實例易於外界訪問,從而方便實例個數的控制並節約系統資源。

單例模式通用類圖

在這裏插入圖片描述
八種單例設計模式使用方式及優缺點

一、餓漢式

public final class Singleton{
    private Singleton(){}
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance(){
        return instance;
    }
}

  類變量instance,由於通過new關鍵字主動始用,會在類初始化時被收集進方法,在多線程情況下不會被實例化多次,能夠保證多線程環境下的唯一實例,但是如果一個類的成員佔用資源比較多,使用這種方式就有所不妥,並且餓漢式單例也不能進行懶加載。
二、餓漢式變種

public final class Singleton{
    private static Singleton instance = null;
    static{
        instance = new Singleton();
    }
    public static Singlton getInstance(){
        return instance;
    }
}

三、懶漢式

public final class Singleton{
    private Singleton(){}
    private static Singleton instance = null;
    public static Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

  在Singleton類初始化的時候並不會導致instance實例化,在多線程環境下,會導致instance實例被實例化一次以上,不會保證單例的唯一性
四、懶漢式+同步方法

public final class Singleton{
    private Singleton(){}
    private static Singleton instance = null;
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

  採用懶漢式+數據同步的方式,既滿足了懶加載又保證了單例的唯一性,但是由於同步方法在多線程環境下會使得在同一時刻只能有同一個線程訪問,導致性能比較低。
六、Double-Check

public final class Singleton{
    Connection conn;
    Socket socket;
    private static Singleton instance = null;
    private Singleton(){
        this.conn;
        this.socket;
    }
    public static Singleton getInstance(){
        if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton;
                }
            }
        }
         return instance;
    }
}

  Double-Check既滿足了懶加載,又能保證實例的唯一性,提供了高效的數據同步策略,但是在多線程環境下,有可能出現空指針異常。
  在Singleton的構造函數中,需要分別實例化conn和socket,還有Singleton自身,但是由於JVM在運行時的指令重排序和Happens-before規則,這三者的實例化順序並無前後約束關係,極有可能instance最先被實例化,但是conn和socket還沒有實例化。未完成實例化的實例,調用其方法將會拋出空指針異常。因此我們可以通過 private volatile static Singleton instance = null 使用volatile關鍵字來禁止指令重排序。
七、靜態內部類

public final class Singleton{
    private Singleton(){}
    public static class Holder{
        public static Singleton instance = new Singleton();
    }
    public static Singleton getInstance(){
        return Holder.instance;
    }
}

  Singleton初始化的時候並沒有創建其實例,Singleton在靜態內部類中定義了實例,並且直接進行了實例化,當靜態內部類主動引用的時候會被實例化,Singleton實例化的過程,在JVM程序編譯器將其收集到方法中,該方法是同步方法,同步方法可以保證內存可見性,JVM指令的順序行和原子性。
八、枚舉方式(https://blog.csdn.net/moakun/article/details/80688851)

public final class Singleton{
    private Singleton(){}
    public enum EnumHolder{
        INSTANCE;
        private Singleton instance;
        private EnumHolder(){
            this.instance = new Singleton();
        }
        public static Singleton getSingleton(){
            return instance;
        }
    }
    public static Singleton getInstance(){
        return EnumHolder.INSTANCE.getSingleton();
    }
}

  枚舉單例實現方式比較精簡,既能保證線程安全,又能實現懶加載,而且枚舉可以避免反序列化破壞單例
  我們知道普通的Java類在反序列化的過程中,回到用類的構造函數來初始化對象,所以即使單例中構造函數私有的,也會被反射給破壞掉。由於反序列化後的對象是重新new出來的,所以就破壞了單例,而枚舉在在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定製的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章