設計模式文章陸續更新
作為程序猿這種特殊物種來說,都掌握了一種特殊能力就是編程思想,邏輯比較謹慎,但是有時候總會忽略到一些細節,比如我,一直以來總覺得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中你的單例是不是一直在裸奔