單例模式(Singleton)是一種使用率非常高的設(shè)計(jì)模式次泽,其主要目的在于保證某一類在運(yùn)行期間僅被創(chuàng)建一個(gè)實(shí)例变秦,并為該實(shí)例提供了一個(gè)全局訪問(wèn)方法成榜,通常命名為getInstance()方法。單例模式的本質(zhì)簡(jiǎn)言之即是:
控制實(shí)例數(shù)目
以Java為例蹦玫,單例模式通呈昊椋可分為餓漢式和懶漢式兩種常規(guī)實(shí)現(xiàn)方式
餓漢式單例實(shí)現(xiàn)
餓漢式顧名思義,就是對(duì)類實(shí)例(食物樱溉?)的需求非常強(qiáng)烈挣输,因此,在裝載該單例類的時(shí)候就會(huì)創(chuàng)建類實(shí)例福贞。如下
public class Singleton {
/**
* 裝載時(shí)即創(chuàng)建類實(shí)例撩嚼,并保存在類變量instance中
* 加上static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
*/
private static Singleton instance = new Singleton();
/**
* 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 加上static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
// 由于類實(shí)例在類裝載時(shí)已被創(chuàng)建并保存在instance中,因此可直接返回
return instance;
}
}
事實(shí)上完丽,在Android開(kāi)發(fā)中恋技,Android Studio提供了一個(gè)直接創(chuàng)建單例類的功能(File->new->Singleton),該功能自動(dòng)生成的單例類正是采用了餓漢式的實(shí)現(xiàn)方式
懶漢式單例實(shí)現(xiàn)
說(shuō)到懶逻族,我們自然而然會(huì)想到拖延癥這一惡習(xí)蜻底,這一點(diǎn)和懶漢式的單例實(shí)現(xiàn)方式相似,這一實(shí)現(xiàn)方式會(huì)一直等到真正需要使用對(duì)象實(shí)例的時(shí)候再去創(chuàng)建該實(shí)例聘鳞。如下
public class Singleton {
/**
* 裝載時(shí)不創(chuàng)建類實(shí)例薄辅,但需要利用一個(gè)類變量去保存后續(xù)創(chuàng)建的類實(shí)例
* 添加static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
*/
private static Singleton instance = null;
/**
* 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
// 如果instance未被初始化抠璃,則初始化該類實(shí)例
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
事實(shí)上站楚,雖然我們前面拿拖延癥來(lái)與懶漢式做類比,但懶漢式的拖延卻是實(shí)際開(kāi)發(fā)中的一種較為常見(jiàn)的節(jié)省資源的方式鸡典,即延遲加載思想源请。這一思想的核心在于直到需要使用某些資源或數(shù)據(jù)時(shí)再去加載該資源或獲取該數(shù)據(jù),這樣可以盡可能地節(jié)省使用前的內(nèi)存空間
線程安全的懶漢式單例實(shí)現(xiàn)
不難分析出彻况,當(dāng)外部多個(gè)線程同時(shí)想要獲取單例類實(shí)例時(shí)谁尸,上述懶漢式實(shí)現(xiàn)方式便很容易導(dǎo)致并發(fā)問(wèn)題。通常有如下幾種改進(jìn)方式
添加synchronized關(guān)鍵詞
....
public static synchronized Singleton getInstance() {
....
這種改進(jìn)方式是最簡(jiǎn)單的纽甘,但由于外部每次調(diào)用getInstance()方法時(shí)均需進(jìn)行判斷良蛮,因此該方式也是效率較低的
利用雙重檢查加鎖機(jī)制
雙重檢查加鎖機(jī)制分為如下兩重檢查
- 在程序每次調(diào)用getInstance()方法時(shí)先不進(jìn)行同步,而是在進(jìn)入該方法后再去檢查類實(shí)例是否存在悍赢,若不存在則進(jìn)入接下來(lái)的同步代碼塊
- 進(jìn)入同步代碼塊后將再次檢查類實(shí)例是否存在决瞳,若不存在則創(chuàng)建一個(gè)新的實(shí)例
這樣一來(lái),就只需要在類實(shí)例初始化時(shí)進(jìn)行一次同步判斷即可左权,而非每次調(diào)用getInstance()方法時(shí)都進(jìn)行同步判斷皮胡,大大節(jié)省了時(shí)間,具體實(shí)現(xiàn)如下
public class Singleton {
/**
* 裝載時(shí)不創(chuàng)建類實(shí)例赏迟,但需要利用一個(gè)類變量去保存后續(xù)創(chuàng)建的類實(shí)例
* 添加volatile關(guān)鍵詞使其不會(huì)被本地線程緩存屡贺,保證線程能正確處理
* 添加static關(guān)鍵詞使得該變量能在getInstance()靜態(tài)方法中使用
*/
private volatile static Singleton instance = null;
/**
* 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
// 第一重檢查:如果instance未被初始化锌杀,則進(jìn)入同步代碼塊
if (instance == null) {
// 同步代碼塊甩栈,保證線程安全
synchronized (Singleton.class) {
// 第二重檢查:如果instance未被初始化,則初始化該類實(shí)例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
利用Java緩存思想實(shí)現(xiàn)的單例實(shí)現(xiàn)
public class Singleton {
// 類實(shí)例緩存KEY值
private static final String KEY = "CACHE";
// 類實(shí)例緩存容器
private static Map<String, Singleton> map = new HashMap<>();
/**
* 私有化構(gòu)造方法糕再,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
// 嘗試從緩存容器中獲取類實(shí)例
Singleton instance = map.get(KEY);
// 未能獲取類實(shí)例量没,則初始化該實(shí)例,并將其緩存至容器中
if (instance == null) {
instance = new Singleton();
map.put(KEY, instance);
}
return instance;
}
}
上述實(shí)現(xiàn)方式暫未考慮線程安全問(wèn)題突想。事實(shí)上殴蹄,利用緩存來(lái)實(shí)現(xiàn)的單例模式其最大的優(yōu)點(diǎn)在于對(duì)單例模式進(jìn)行擴(kuò)展究抓。我們自然而然地可以想到這么一種情況,既然在實(shí)際開(kāi)發(fā)中經(jīng)常需要保證某個(gè)類只能被創(chuàng)建一個(gè)實(shí)例饶套,那么漩蟆,會(huì)不會(huì)出現(xiàn)保證某個(gè)類只能被創(chuàng)建兩個(gè)或多個(gè)實(shí)例這種需求呢?對(duì)于這項(xiàng)需求妓蛮,我們首先可以想到怠李,上述實(shí)現(xiàn)方式中所建立的緩存容器是可以存儲(chǔ)多個(gè)類實(shí)例的,利用這一特點(diǎn)蛤克,只需考慮一個(gè)問(wèn)題捺癞,即外部調(diào)用時(shí)到底需要為其返回哪一個(gè)實(shí)例,便可實(shí)現(xiàn)“雙例模式”以及“多例模式”(原諒我為它們?nèi)×艘恍┢婀值拿郑┝斯辜罚唧w實(shí)現(xiàn)如下
public class Singleton {
// 可創(chuàng)建的最大類實(shí)例數(shù)髓介,這里以“雙例模式”為例
private static final int MAX = 2;
// 類實(shí)例緩存KEY值
private static final String KEY = "CACHE";
// 當(dāng)前正在使用的實(shí)例序號(hào)
private static int index = 1;
// 類實(shí)例緩存容器
private static Map<String, Singleton> map = new HashMap<>();
/**
* 私有化構(gòu)造方法,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
// 嘗試從緩存容器中獲取第index個(gè)類實(shí)例
String key = KEY + index;
Singleton instance = map.get(key);
// 未能獲取類實(shí)例筋现,則初始化該實(shí)例唐础,并將其緩存至容器相應(yīng)index中
if (instance == null) {
instance = new Singleton();
map.put(key, instance);
}
// 這里以最基本的順序調(diào)用為例,其他復(fù)雜調(diào)度方式不加討論矾飞,具體調(diào)用方式如下
// index++一膨,以在下一次調(diào)用中獲取下一個(gè)類實(shí)例,當(dāng)達(dá)到類實(shí)例數(shù)上限時(shí)洒沦,重新獲取第一個(gè)類實(shí)例
if ((++index) > MAX) {
index = 1;
}
return instance;
}
}
單例模式的最佳實(shí)現(xiàn)
綜合而言豹绪,上述實(shí)現(xiàn)方式都或多或少地存在諸如線程不安全、無(wú)法做到延遲加載等小缺陷申眼。這里給出一個(gè)可以稱得上完美的最佳解決方案
Lazy Initialization Holder Class 模式
這一方案的核心在于Java的類級(jí)內(nèi)部類(即使用static關(guān)鍵詞修飾的內(nèi)部類瞒津,否則稱之為對(duì)象級(jí)內(nèi)部類)以及多線程缺省同步鎖,先來(lái)看看具體實(shí)現(xiàn)
public class Singleton {
/**
* 類級(jí)內(nèi)部類括尸,用于緩存類實(shí)例
* 該類將在被調(diào)用時(shí)才會(huì)被裝載巷蚪,從而實(shí)現(xiàn)了延遲加載
* 同時(shí)由于instance采用靜態(tài)初始化的方式,因此JVM能保證其線程安全性
*/
private static class Instance {
private static Singleton instance = new Singleton();
}
/**
* 私有化構(gòu)造方法濒翻,使外部無(wú)法通過(guò)構(gòu)造方法構(gòu)造除instance外的類實(shí)例
* 從而達(dá)到單例模式控制類實(shí)例數(shù)目的目的
*/
private Singleton() {
}
/**
* 類實(shí)例的全局訪問(wèn)方法
* 添加static關(guān)鍵詞使得外部可以通過(guò)類名直接調(diào)用該方法獲取類實(shí)例
* @return 單例類實(shí)例
*/
public static Singleton getInstance() {
return Instance.instance;
}
}
在前面提到的餓漢式實(shí)現(xiàn)方式中钓辆,我們利用Java的靜態(tài)初始化、借由JVM實(shí)現(xiàn)了線程安全肴焊,因此這里同樣采用了這種方式。而另一方面功戚,為了避免餓漢式實(shí)現(xiàn)中無(wú)法進(jìn)行延遲加載的缺陷娶眷,我們構(gòu)造了一個(gè)類級(jí)內(nèi)部類來(lái)緩存類實(shí)例,由于該類只會(huì)在通過(guò)getInstance()方法去調(diào)用時(shí)才會(huì)被系統(tǒng)裝載啸臀,換言之届宠,只有初次調(diào)用getInstance()方法時(shí)才會(huì)去初始化類實(shí)例烁落,因此也實(shí)現(xiàn)了延遲加載這一功能。如此便可使得這一實(shí)現(xiàn)方式能夠同時(shí)具備線程安全豌注、延遲加載以及節(jié)省大量同步判斷資源等優(yōu)勢(shì)伤塌,可以說(shuō)是單例模式的最佳實(shí)現(xiàn)了