java中你確定用對單例了嗎?

設計模式文章陸續更新

java代理模式
java工廠模式
java狀態模式

作為程序猿這種特殊物種來說,都掌握了一種特殊能力就是編程思想,邏輯比較謹慎,但是有時候總會忽略到一些細節,比如我,一直以來總覺得Singleton是設計模式里最簡單的,不用太在意,然而就是因為這種不在意在開發中吃虧了.真的too young to simple.
好不扯淡了,直入主題.

在代碼的世界里發現有各種寫法的單例,有人說單例有5種,6種,7種...
對于單例的分類這點必須規范下,首先這么多種的分類是根據什么來定義的,基準是什么?否則怎么會有那么多寫法.

因此歸納下來,從延遲加載執行效率的角度出發主要也就分為兩種,餓漢顧名思義就是執行效率高,但缺乏延時加載,其他寫法差不多都是懶漢式的一個拓展,或者優化而演化出來的,下面請看代碼.

開發中常用的單例-餓漢式

public class SingletonDemo1 {

    private static final SingletonDemo1 s1 = new SingletonDemo1();

    public static SingletonDemo1 getInstance() {
        return s1;
    }

    private SingletonDemo1() {
    }
}

沒錯上面這塊代碼叫做單例-餓漢式,餓漢式一直以效率高而聞名于單例界,因此咋們開發中常用的單例模式也會選擇他,簡單而好用.

開發評價: ★★★★☆
延時加載: ★☆☆☆☆
執行效率: ★★★★★

耗時的蝸牛-懶漢式

public class SingletonDemo2 {
    private static SingletonDemo2 s1;

    public static synchronized SingletonDemo2 getInstance() {
        if (s1 == null) {
            s1 = new SingletonDemo2();
        }
        return s1;
    }

    private SingletonDemo2() {
    }
}

hello world這個世界里都知道這種單例基本不會去用,在<head first設計模式>中說到:同步getInstance()的方法既簡單又有效。但是你必須知道,同步一個方法可能造成程序執行效率下降100倍。因此,如果getInstance()使用頻繁的話,就需要考慮其他方法了.所以我們沒有必要因空間而失去時間,在這個用戶體驗的時代不值得.

開發評價: ★☆☆☆☆
延時加載: ★★☆☆☆
執行效率: ★☆☆☆☆

double check雙重檢查鎖-懶漢式

這可以說是上面餓漢式的一個縮影,為什么這么說,因為他并不完美,仍然有bug.

public class SingletonDemo3 {
    private static SingletonDemo3 s1;

    public static SingletonDemo3 getInstance() {
        if (s1 == null) {
        //這里使用了臨時變量
            SingletonDemo3 instance;
            synchronized (SingletonDemo3.class) {
                instance = s1;
                if (instance == null) {
                    synchronized (SingletonDemo3.class) {
                        if (instance == null) {
                            instance = new SingletonDemo3();
                        }
                    }
                    s1 = instance;
                }
            }
        }
        return s1;
    }
}

這個方式主要是通過if判斷非null實例,提高了執行的效率,不必每次getInstace都要進行synchronize,只要第一次要同步,有沒創建了不用.

但是為什么說這種寫法有bug?這個問題主要是java的jvm層內部模型引起的.簡單點說就是instance引用的對象有可能還沒有完成初始化完就直接返回該實例過去,在jdk1.5后這個問題才得到了優化,這不多說,可以看看這篇博文講得不錯.
詳情見

當然也有了一些解決方法

  • 使用volatile關鍵字解決雙重檢查鎖定的bug,對于volatile關鍵字就是Java中提供的另一種解決可見性和有序性問題的方案.
public class SafeDoubleCheckedLocking {
//添加了volatile關鍵字
    private volatile static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SafeDoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();//instance為volatile,現在沒問題了
            }
        }
        return instance;
    }
}

開發評價: ★★★☆☆
延時加載: ★★★☆☆
執行效率: ★★★☆☆

推薦使用的靜態內部類-懶漢式

public class SingletonDemo4 {
    
    //通過靜態內部類的方式來實例化對象
    private static class InnerSingleton {
        private static final SingletonDemo4 instance = new SingletonDemo4();
    }

    public static  SingletonDemo4 getInstance() {
        return InnerSingleton.instance;
    }

    private SingletonDemo4() {
    }
}

這周方式是利用了類加載的一些特性,在classloder的機制中,初始化instance時只有一個線程,而且這種方式還有一個好處就是起到了延時加載的效果,當SingletonDemo4被加載了,但是內部類InnerSingleton并不會被加載,因為InnerSingleton沒有主動使用,只有通過調用getInstance方法時才會去加載InnerSingleton類,進而實例private static final SingletonDemo4 instance = new SingletonDemo4();
因此這種巧妙的方式比起雙重檢查鎖來說,安全來又高效了些.

開發評價: ★★★★☆
延時加載: ★★★★☆
執行效率: ★★★★☆

推薦使用的枚舉-餓漢式

public enum SingletonDemo5 {
    
    //枚舉元素本身就是一個單例(名字可以隨意定義);
    INSTANCE;

    //可以做一些單例的初始化操作
    public void singletonOperation() {
    }
}

這種方式其實很帥,但是在實際開發中很少人使用過,這點有點奇怪,首先enum本身就是一個單例,而且枚舉還有一個特性,可以避免放序列化和反射破解單例問題,經理再也不用擔心單例安全了,可能是1.5才有enum的原因吧,如果項目適合的話可以試下這種單例.

開發評價: ★★★★☆
延時加載: ★★★★☆
執行效率: ★★★★☆

總結一下:

對于下面的單例總的來說各有各的優點,至于開發中使用哪個可以根據你的業務需求來選擇.

  • 餓漢
    • 標準餓漢 (安全防護方面 枚舉單例更優于標準餓漢)
      線程安全,高效,不可以懶加載
    • 枚舉單例
      線程安全,高效,不可以懶加載(天然避免反射與反序列化)
  • 懶漢 (效率方面 靜態內部類更優于標準懶漢)
    • 標準懶漢
      線程安全,低效,可以懶加載
    • 雙重檢測(不推薦,有bug)
      線程安全,低效,可以懶加載
    • 靜態內部類
      線程安全,低效,可以懶加載

對于單例的安全性問題,可以繼續你那帥氣的閱讀姿勢, java中你的單例是不是一直在裸奔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 單例模式(SingletonPattern)一般被認為是最簡單、最易理解的設計模式,也因為它的簡潔易懂,是項目中最...
    成熱了閱讀 4,293評論 4 34
  • 版權聲明:本文為博主原創文章,未經博主允許不得轉載 PS:轉載請注明出處作者: TigerChain地址: htt...
    TigerChain閱讀 1,344評論 0 3
  • 1 單例模式的動機 對于一個軟件系統的某些類而言,我們無須創建多個實例。舉個大家都熟知的例子——Windows任務...
    justCode_閱讀 1,477評論 2 9
  • 1 場景問題# 1.1 讀取配置文件的內容## 考慮這樣一個應用,讀取配置文件的內容。 很多應用項目,都有與應用相...
    七寸知架構閱讀 6,885評論 12 68
  • 不是一個圈子不要硬逼迫自己合群,我和那屋的兩位領導真的三觀不合呀,在他們眼里我傻的可以跟HelloKitty一樣,...
    大胡子呢閱讀 220評論 0 0