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
這個世界裏都知道這樣的單例基本不會去用,在
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中你確定用對單例了嗎?