1. 程式人生 > >java中你確定用對單例了嗎?

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

其它 safe 一個 with public 也會 sim data- 可見性

作為程序員這樣的特殊物種來說,都掌握了一種特殊能力就是編程思想,邏輯比較慎重,可是有時候總會忽略到一些細節,比方我,一直以來總認為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這個世界裏都知道這樣的單例基本不會去用,在

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中你的單例是不是一直在裸奔

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