以下是五種單例模式,開發中都會用到
一、例項
/**
* User:xijiufu
* Time:2016/4/4,0:00
* Function : 普通員工
*/
public class Staff {
public void work() {
//幹活
}
}複製程式碼
/**
* User:xijiufu
* Time:2016/4/4,0:01
* Function :副總裁
*/
public class VP extends Staff {
@Override
public void work() {
//管理下面的人
}
}複製程式碼
/**
* User:xijiufu
* Time:2016/4/4,14:11
* Function : 惡漢模式
*/
public class CEO extends Staff {
private static final CEO mCeo = new CEO();
private CEO() {
}
public static CEO getCeo() {
return mCeo;
}
@Override
public void work() {
//管理VP
}
}複製程式碼
/**
* User:xijiufu
* Time:2016/4/4,14:12
* Function :公司
*/
public class Company {
private List<Staff> allStaffs = new ArrayList<>();
public void addStaff(Staff per) {
allStaffs.add(per);
}
public void showAllStaffs() {
for (Staff per : allStaffs) {
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:" + per.toString());
}
}
}複製程式碼
測試結果:
public class SingletonTest {
public static void main(String[] strings) {
Company cp = new Company();
Staff ceo1 = CEO.getCeo();
Staff ceo2 = CEO.getCeo();
cp.addStaff(ceo1);
cp.addStaff(ceo2);
Staff vp1 = new VP();
Staff vp2 = new VP();
Staff staff1 = new Staff();
Staff staff2 = new Staff();
Staff staff3 = new Staff();
cp.addStaff(vp1);
cp.addStaff(vp2);
cp.addStaff(staff1);
cp.addStaff(staff2);
cp.addStaff(staff3);
cp.showAllStaffs();
}
}複製程式碼
輸出的結果是:
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.CEO@78308db1
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.CEO@78308db1
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.VP@27c170f0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.VP@5451c3a8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@2626b418
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@5a07e868
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Staff:com.mrxi.detailed.tools.design_pattern.singleton.Staff@76ed5528
從上述的程式碼中可以看出,CEO類不能通過new的形式構造物件,只能通過CEO.getCEO() 函式來 獲取,而這個CEO物件是靜態物件,並且在宣告的時候就已經初始化了,這就保證了CEO物件的唯一性。從輸出的結果可以看出,兩次CEO輸出的物件都是一樣的,VP與Staff物件是不同的。這個實現的核心在於將CEO類的構造方法私有化,使得外部程式不能通過建構函式來構造CEO物件,而CEO類通過一個靜態方法返回一個靜態物件。
二、單例模式
(1)、懶漢模式
懶漢模式是宣告一個靜態物件,並且在使用者第一次呼叫getInstance時進行初始化,而上述的餓漢模式是在宣告靜態物件時就已經初始化,懶漢模式實現如下:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}複製程式碼
根據上面的程式碼發現getInstance方法中新增了synchronized關鍵字,也就是getInstance是一個同步方法,這就是上面所說的在多執行緒情況下保證單例物件唯一性的手段,細想幾十instance已經被初始化,第一次呼叫時就會被初始化instance,每次呼叫getInstance方法都會進行同步,這樣就消耗不必要的資源,這也是懶漢模式存在的問題。
懶漢模式的優點是單例只有在使用時才會被例項化,在一定程度上節約了資源,缺點是第一次載入時需要及時進行例項化,反應稍慢,最大的問題是每次呼叫getInstance都進行同步,造成不必要的同步開銷,所有不建議使用這種模式。
(2)、Double CheckLock (DCL) 實現單例
DCL方式實現單例模式的優點是既能夠在需要時才初始化單例,又能夠保證執行緒安全,且單例物件初始化後呼叫getInstance不進行同步鎖。
/**
* User:xijiufu
* Time:2016/4/4,14:37
* Function :Double CheckLock DCL雙重檢查鎖定
*/
public class Singleton2 {
private static Singleton2 sInstance = null;
private Singleton2() {
}
public void doSomething() {
System.out.println("so sth.");
}
public static Singleton2 getInstance() {
if (sInstance == null) {
synchronized (Singleton2.class) {
if (sInstance == null) {
sInstance = new Singleton2();
}
}
}
return sInstance;
}
}複製程式碼
可以看到getInstance方法中對instance進行了兩次判空:第一層主要為了避免不必要的同步,第二層的判斷則是為了在null的情況下建立例項。
為什麼呢?
假設執行緒A執行到了instance=new Singleton2 ()語句,這裡看起來是一句程式碼,但實際上它並不是一個原子操作,什麼是原子操作?可以看這兒(baike.baidu.com/item/原子操作?f…),這句程式碼最終會被編譯成多條彙編指令,大致做了三件事:
①、給Singleton的例項分配記憶體;
②、呼叫Singleton()的建構函式,初始化成員欄位;
③、將instance物件指向分配的記憶體空間(此時instance就不是null);
DCL的優點:資源利用率高,第一次執行getInstance時單例物件才會被例項化,效率高。
缺點:第一次載入時反應稍慢,也由於Java記憶體模型的原因偶爾會失敗。在高併發環境下也有一定的缺陷,雖然發生的概率很小。DCL模式是使用最多的單例實現方式,它能夠在需要時才例項化單例物件,並且能夠在絕大多數場景下保證單例物件的唯一性,除非你的程式碼在併發場景比較複雜或者低於jdk1.6版本下使用,否則這種方式一般能夠滿足需求。
(3)、 靜態內部類單例模式
DCL雖然在一定程度上解決了資源消耗、多餘的同步、執行緒安全等問題,但是,它還是在某些情況下出現失效的問題。這個問題被稱為雙重檢查鎖定(DCL)失效,在《Java併發程式設計實踐》一書的最後談到這個問題,並指出這種“優化”是醜陋的,不贊成使用,建議使用如下程式碼:
/**
* User:xijiufu
* Time:2016/4/4,14:47
* Function : 靜態內部類單例模式
*/
public class Singleton3 {
private Singleton3() {
}
public static Singleton3 getInstance() {
return SingletonHolder.sInstance;
}
//靜態內部類
private static class SingletonHolder {
private static final Singleton3 sInstance = new Singleton3();
}
}複製程式碼
當第一次載入Singleton類時並不會初始化sInstance,只有在第一次呼叫Singleton的getInstance方法時才會導致sInstance被初始化。因此,第一次呼叫getInstance方法會導致虛擬機器載入SingletonHolder類,這種方式不僅能夠確保執行緒安全,也能夠保證單例物件的唯一性,同時也延遲了單例的例項化,所以這是推薦使用的單例模式實現方式。
(4) 、列舉單例
前面說了三種單例模式實現,但是這些實現方式不是稍顯麻煩就是會在某些情況下出問題。還有沒有更簡單的實現方式呢?下面的程式碼:
/**
* User:xijiufu
* Time:2016/4/4,14:50
* Function :
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println(">>>>>>>>>>>>>>>>do.sth");
}
}複製程式碼
列舉!
寫法簡單是列舉單例最大的優點,列舉在Java中與普通的類是一樣的,不僅能夠有欄位,還能夠有自己的方法。最重要的是預設列舉例項的建立是執行緒安全的,並且在任何情況下它都是一個單例。為什麼?在上述的幾種單例中,在一個情況下它們會出現重新建立物件的情況,那就是反序列化。
通過反序列化可以將一個單例的例項物件寫到磁碟,然後再讀回來,從而有效獲得一個例項。即使建構函式是私有的,反序列化時依然可以通過特殊的途徑去建立類的一個新的例項,相當於呼叫該類的建構函式。反序列化操作提供了一個很特別的鉤子函式,類中具有一個私有的、被例項化的方法readResolve,這個方法可以讓開發人員控制物件的反序列化。例如:上述幾個例項中如果要杜絕單例物件在被飯序列化重新生成物件,那麼必須加入如下方法:
private Object readResolve() throws ObjectStreamException {
return sInstance;
}複製程式碼
也就是readResolve方法中將instance物件返回,而不是預設的重新生成一個新的物件。而對於列舉,並不存在這個問題,因為即使使用反序列化它也不會重新生成新的例項。
(5)、使用容器實現單例模式
通過以上的單例 模式之後 ,再來看看一種另類的實現,程式碼如下:
/**
* User:xijiufu
* Time:2016/4/4,14:55
* Function :使用容器實現單例模式
*/
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}複製程式碼
在程式的初始化時,將多種單例型別注入到一個統一的管理類中,在使用時根據key獲取物件對應型別的物件。這種方式使得我們可以管理多種型別的單例,並且在使用時也可以通過統一的介面進行獲取操作,降低了使用者的使用成本,也對使用者隱藏了具體實現,降低了耦合度。
不管以哪種形式實現單例模式,他們的核心原理都是講建構函式私有化,並且通過靜態方法獲取一個唯一的例項,在這個獲取的過程中必須保證執行緒安全、防止反序列化導致重新生成例項物件等問題。選擇哪種實現方式取決於專案本身,如是否是複雜的併發環境、jdk版本是否過低、單例物件的資源消耗等。
三、Android原始碼中的單例模式
在Android系統中,我們經常會通過Context獲取系統級別的服務,如WindowsManagerService、ActivityManagerService等,更常用的是一個LayoutInflater的類,這些服務會在合適的時候以單例的形式註冊在系統中,在我們需要的時候通過Context的getSystemService(String name)獲取。