你瞭解單例模式的最佳實踐嗎?

『單例模式』是一種創建型的設計模式,保證一個類只有一個實例,並提供一個訪問它的全局訪問點。

在一個系統中,一個類經常會被使用在不同的地方,通過單例模式,我們可以避免多次創建多個實例,從而節約系統資源。

單例模式往往有三個特徵,一個類只能有一個實例,它必須自行提供實例的創建,它必須提供方法暴露此實例。

餓漢方式

餓漢總是一次喫個飽,所以這種方式總是在系統初始化的時候創建所有的對象,不管會不會何時被使用

public class SingleTon {

    //自行構造實例
    private static final SingleTon instance = new SingleTon();

    //置空構造器,不允許外部構造
    public SingleTon(){}

    //對外暴露內部實例
    public SingleTon getInstance(){
        return this.instance;
    }
}

餓漢方式實現的單例模式是極其簡單的,但缺點也很明顯,即便這個類一時半會不會被使用到,但也必須在編譯的時候初始化分配堆內存,創建這個內部實例。

懶漢方式

懶漢很懶,只有在系統用到某個類的實例的時候,纔會實例化出一個唯一實例。

public class SingleTon {

    private static SingleTon instance= null;
    
    private SingleTon(){}
    
    public static SingleTon getInstance(){
        if(null == instance){
            instance = new SingleTon();
        }
        return instance;
    }
}

instance 在類編譯的時候沒有初始化,而只有在調用 getInstance 方法的時候,纔會去實例化 instance。

looks pretty!

多線程環境下,線程 A 和線程 B 同時判斷 instance==null,都去實例化 instance,導致 instance 被實例化兩次,堆中產生一個無引用對象,併發量大的情況下,會有更多的無用對象被創建,甚至可能提前觸發 GC。

懶漢方式優化一(加本地鎖)

線程不安全,相信你第一時間也會想到加鎖控制,那你是不是也這麼加的呢?

public class SingleTonLock {

    private static SingleTonLock instance= null;

    public SingleTonLock(){}

    public synchronized SingleTonLock getInstance(){
        if (instance == null){
            instance = new SingleTonLock();
        }
        return instance;
    }
}

這種方式直接給 getInstance 方法加鎖了,很明顯,會造成大量無效的鎖等待,繼續優化。

public class SingleTonLock {

    private static volatile SingleTonLock instance= null;

    public SingleTonLock(){}

    public SingleTonLock getInstance(){
        if (instance == null){
            synchronized(this){
                //再次判斷是爲了防止有的線程醒來以後再次實例化
                //有可能其他線程已經實例化完成了
                if (instance == null){
                    instance = new SingleTonLock();
                }
            }
        }
        return instance;
    }
}

給 instance 加 volatile 修飾是爲了防止 jvm 指令重排序,通過再次判斷可以保證此實例的唯一實例化。

這的確是一種不錯的懶漢實例,推薦大家使用,但我更推薦下一種。

懶漢方式優化二(枚舉類)

個人認爲使用枚舉類實現懶漢單例模式是最佳實踐,枚舉類本質上是用靜態字段來實現的,例如:

public enum Color {
    RED(), GREEN(), BLUE(), YELLOW();
}

javap 反編譯這個枚舉類得到:

public final class com.example.test.lazy.Color extends java.lang.Enum<com.example.test.lazy.Color> {
  public static final com.example.test.lazy.Color RED;
  public static final com.example.test.lazy.Color GREEN;
  public static final com.example.test.lazy.Color BLUE;
  public static final com.example.test.lazy.Color YELLOW;
  public static com.example.test.lazy.Color[] values();
  public static com.example.test.lazy.Color valueOf(java.lang.String);
  static {};
}

那麼,枚舉如何實現單例模式,上代碼:

public class SingleTonE {

    public static SingleTonE getInstance(){
        return SingleTonEnum.SINGLETON.getInstance();
    }

    private enum SingleTonEnum{
        SINGLETON;

        private SingleTonE instance;

        SingleTonEnum(){
            instance = new SingleTonE();
        }

        public SingleTonE getInstance(){
            return this.instance;
        }
    }
}

只有當調用 getInstance 方法獲取實例的時候,纔會觸發枚舉類的加載,然後按照上面說的,生成一個靜態字段並初始化其內部的單例 instance,因爲 jvm 保證只能一個線程進行類加載,所以整個過程看起來非常的簡單。

個人認爲,枚舉類實現單例模式是一種最佳實踐,推薦你應用到自己的項目。

近期會整理一個設計模式系列,分別講講 23 種設計模式,感興趣的可以關注下哦~


關注公衆不迷路:有詩有酒有代碼。

公衆號回覆「1024」加作者微信一起探討學習!

公衆號回覆「面試題」送你一份面試題以及作者的作答答案

每篇文章用到的所有案例代碼素材都會上傳我個人 github

https://github.com/SingleYam/overview_java

歡迎來踩!

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