單例模式定義
確保某一個類之后一個實例,而且自行實例化并向整個系統(tǒng)提供服務(wù)。
單例模式使用場景
確保某個類有且只有一個對象的場景,避免產(chǎn)生多個對象消耗過多的資源,或者某種類型的對象只應(yīng)該有且只有一個。例如,創(chuàng)建一個對象需要消耗的資源過多,如要訪問IO和數(shù)據(jù)庫等資源,這時就要考慮使用單例模式。
單例模UML類圖

角色介紹:
- Client - 客戶端
- Singleton - 單例類
實現(xiàn)單例模式主要注意的關(guān)鍵點:
- 構(gòu)造函數(shù)不能對外開放,一般為Private
- 通過一個靜態(tài)方法或者枚舉返回單例類對象;
- 確保單例類的對象有且只有一個,尤其是在多線程的環(huán)境下;
- 確保單例類對象在反序列化時不會重新構(gòu)建對象。
通過將單例類的構(gòu)造方法私有化,使得客戶端代碼不能通過new 的形式手動構(gòu)造單例類的對象,單例類會暴露一個公有的靜態(tài)方法,客戶端需要調(diào)用這個靜態(tài)方法獲取到單例類的唯一對象,在獲取這個單例類對象的過程中需要確保線程安全,即在多線程環(huán)境下構(gòu)造單例類的對象也是有且只有一個,這也是單例模式實現(xiàn)中比較困難的地方。
單例類的實現(xiàn)
1.餓漢式單例
public class Singleton{
/**
* 餓漢式單例
*/
private static final Singleton singleton = new Singleton();
/**
* 私有化構(gòu)造方法
*/
private Singleton() {
}
public static Singleton getSingleton() {
return singleton;
}
}
2.懶漢式單例
懶漢模式是聲明一個靜態(tài)對象,并且在用戶第一次調(diào)用getSingleton() 時進行初始化,而上述餓漢模式是在聲明靜態(tài)對象時就已經(jīng)初始化。懶漢模式實現(xiàn):
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){
}
public static synchronized LazySingleton getInstance(){
if (instance==null){
instance = new LazySingleton();
}
return instance;
}
}
getInstance() 是用關(guān)鍵字synchronized 修飾,是一個同步方法,以確保多線程情況下單例類對象唯一性。但是每次調(diào)用getInstance() 時都會進行同步,這樣會消耗不必要的資源,這也是懶漢式單例的最大問題。懶漢式單例模式優(yōu)點是單例只有在使用時才會被初始化,在一定程度上節(jié)約了資源;缺點是第一次加載是需要及時進行實例化,反應(yīng)稍慢。
3.Double Check Lock(DCL)實現(xiàn)單例
DCL 方式實現(xiàn)單例模式的優(yōu)點是既能夠在需要是才初始化,又能保證線程安全,且單例對象初始化后調(diào)用getInstance() 不進行同步鎖。實現(xiàn)如下:
public class DCLSingleton {
private static DCLSingleton singleton;
private DCLSingleton() {
}
public static DCLSingleton getSingleton() {
if (singleton == null) {
synchronized (DCLSingleton.class) {
if (singleton == null) {
singleton = new DCLSingleton();
}
}
}
return singleton;
}
}
DCL模式是使用最多的單例實現(xiàn)方式,它能夠在需要是才實例化單例對象,并且能夠保證對象的唯一性。
4.靜態(tài)內(nèi)部類單例模式
public class StaticInnerSingleton {
public static StaticInnerSingleton getSingleton() {
return SingletonHelp.SINGLRTON;
}
private static class SingletonHelp {
private static final StaticInnerSingleton SINGLRTON = new StaticInnerSingleton();
}
}
當(dāng)?shù)谝淮渭虞dStaticInnerSingleton 類時并不會初始化SINGLRTON ,只有在第一次調(diào)用getSingleton() 時才會導(dǎo)致初始化。因此,第一次調(diào)用getSingleton()會導(dǎo)致虛擬機加載 SingletonHelp 類,這種方式不僅能保證線程安全,也能保證單例對象的唯一性,同時延遲了單例的實例化,所以推薦使用這種方式實現(xiàn)單例模式。
5.枚舉單例
public enum EnumSingleton {
INSINGLE;
}
寫法簡單是枚舉單例的最大優(yōu)點,枚舉在Java 中與普通類一樣,不僅能夠有字段,還能夠有自己的方法。最重要的是默認枚舉實例的創(chuàng)建是線程安全的,并且任何情況下它都是一個單例。
6.使用容器實現(xiàn)單例
public class SingletonManager {
private static HashMap<String, Object> objMap = new HashMap<>();
private SingletonManager() {
}
public static void registerService(String key, Object value) {
if (!objMap.containsKey(key)) {
objMap.put(key, value);
}
}
public Object get(String key) {
return objMap.get(key);
}
}
在程序的初始,將多種單例類型注入到一個統(tǒng)一的管理類中,在使用是根據(jù)key 獲取對應(yīng)類型的對象。這種方式使得我們可以管理多種類型的單例,并且使用時可以通過統(tǒng)一的接口進行獲取操作,降低了用戶的使用成本,也對用戶隱藏了具體實現(xiàn),降低了耦合度。
總結(jié)
不管以那種方式實現(xiàn)單例模式,他們的核心都是將構(gòu)造函數(shù)私有化,并且通過靜態(tài)方法獲取一個唯一的實例,在這個獲取的過程中必須保證線程安全、防止反序列化導(dǎo)致重新生成實例對象等問題。但是由于客戶端通常沒有高并發(fā)的情況,因此,選擇哪種方式實現(xiàn)并不會有太大影響。即便如此,出于效率考慮,推薦使用DCL方式和靜態(tài)內(nèi)部類方式。
優(yōu)點
- 由于單例模式在內(nèi)存中只有一個實例,減少了內(nèi)存開支,特別是一個對象需要頻繁的創(chuàng)建銷毀時,而且創(chuàng)建和銷毀時性能又無法優(yōu)化,單例模式的優(yōu)勢就非常明顯。
- 由于單例模式只生成一個實例,減少了系統(tǒng)的性能開銷,當(dāng)一個對象的產(chǎn)生需要比較多的資源時,如讀取配置、產(chǎn)生其他依賴對象時,則可以通過在應(yīng)用啟動是直接產(chǎn)生一個單例對象,然后永久駐留內(nèi)存的方式解決。
- 單例模式可以避免對資源的多重占用,例如一個寫文件操作,由于只有一個實例存在內(nèi)存中,避免對同一資源文件的同時寫操作。
- 單例模式可以在系統(tǒng)設(shè)置全局的訪問點,優(yōu)化和共享資源訪問,例如,可以設(shè)計一個單例類,負責(zé)所有數(shù)據(jù)表的映射處理。
缺點
- 單例模式一般沒有接口,擴展很困難。
- 單例對象如果持有
Context,那么很容易引發(fā)內(nèi)存泄露,此時需要注意傳遞對象的Context最后是ApplicationContext。
Demo
Android源碼單例模式
參考
《Android源碼設(shè)計模式》