定義:確保一個(gè)類只有一個(gè)實(shí)例,并提供對(duì)該實(shí)例的全局訪問(wèn),其構(gòu)造函數(shù)私有化。
單例模式的七種寫(xiě)法
1、餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
這種方式在類加載時(shí)就完成了初始化,所以類加載較慢,但獲取對(duì)象的速度快。這種方式基于類加載機(jī)制,避免了線程的同步問(wèn)題。在類加載的時(shí)候就完成實(shí)例化,沒(méi)有達(dá)到懶加載的效果。如果從始至終未使用過(guò)這個(gè)實(shí)例,則會(huì)造成內(nèi)存的浪費(fèi)。
2、懶漢模式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
這種懶漢模式聲明了一個(gè)靜態(tài)對(duì)象,在用戶第一次調(diào)用時(shí)初始化。這雖然節(jié)約了資源,但第一次加載時(shí)需要實(shí)例化,反應(yīng)稍慢一些,主要是在多線程時(shí)不能正常工作。
3、懶漢模式(線程安全)
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
這種懶漢模式因?yàn)榧恿送芥i,所以是線程安全的,但是每次調(diào)用getInstance方法時(shí)都需要進(jìn)行同步,這會(huì)造成不必要的同步開(kāi)銷,而且大部分時(shí)候我們是用不到同步的,所以不建議用這種模式。
4、雙重檢查模式(DCL)
public class Singleton {
private static volatile Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
這種寫(xiě)法在getInstance方法中隊(duì)Singleton進(jìn)行了兩次判空:第一次是為了不必要的同步,第二次是在Singleton等于null的情況下才創(chuàng)建實(shí)例。在這里使用volatile,也會(huì)或多或少地影響性能,第一次加載時(shí)反應(yīng)稍慢,在高并發(fā)環(huán)境下也有一定的缺陷。DCL雖然在一定程度上解決了資源的消耗和多余的同步、線程安全等問(wèn)題,但其在某些情況下還是會(huì)出現(xiàn)失效的問(wèn)題。
volatile作用:
(1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
(2)禁止指令重排序優(yōu)化。(什么是指令重排序:是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開(kāi)發(fā)送給各相應(yīng)電路單元處理)。
DCL失效:
當(dāng)一個(gè)A線程執(zhí)行到instance = new Singleton()語(yǔ)句時(shí),這里看起來(lái)是一句代碼,實(shí)際上這不是一個(gè)原子操作,這句代碼最終會(huì)被編譯成多條匯編指令,它大致做了3件事:
(1)給Singleton的實(shí)例分配內(nèi)存;
(2)調(diào)用Singleton()的構(gòu)造函數(shù),初始化成員字段;
(3)將instance對(duì)象指向分配的內(nèi)存空間(此時(shí)instance就不是null了)
但是,由于Java編譯器允許處理器亂序執(zhí)行,以及JDK1.5之前JMM(Java Memory Model,即java內(nèi)存模型)中Cache、寄存器到主內(nèi)存回寫(xiě)順序的規(guī)定,上面的第二和第三的順序是無(wú)法保證的。也就是說(shuō),執(zhí)行順序可能是1-2-3,也可能是1-3-2。如果是后者,并且在3執(zhí)行完畢、2未執(zhí)行之前,被切換到線程B上,這時(shí)候instance因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過(guò)了第三點(diǎn),instance已經(jīng)是非空了,所以,線程B直接取走instance,再使用時(shí)就會(huì)出錯(cuò),這就是DCL失效問(wèn)題。
5、靜態(tài)內(nèi)部類單例模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
第一次加載Singleton是并不會(huì)初始化INSTANCE,只有第一次調(diào)用getInstance方法是虛擬機(jī)加載SingletonHolder并初始化INSTANCE。這樣不僅能確保線程安全,也能保證Singleton類的唯一性,所以是非常推薦的一種單例寫(xiě)法。
6、枚舉單例模式
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}
默認(rèn)枚舉實(shí)例的創(chuàng)建時(shí)線程安全的,并且在任何情況下都是單例。前面講過(guò)的機(jī)制單例模式,有一種情況會(huì)重寫(xiě)創(chuàng)建對(duì)象,那就是反序列化:將一個(gè)單例實(shí)例對(duì)象寫(xiě)到磁盤(pán)再讀取回來(lái),從而獲得了一個(gè)實(shí)例。反序列化操作提供了readResolve方法,這個(gè)方法可以讓開(kāi)發(fā)者控制對(duì)象的反序列化。所以,如果要杜絕單例對(duì)象反序列化時(shí)重寫(xiě)生成對(duì)象,必須加入如下方法:
private Object readResolve() throws ObjectStreamException {
return SingletonHolder.INSTANCE;
}
枚舉單例的優(yōu)點(diǎn)就是簡(jiǎn)單,不過(guò)Android使用enum之后的dex大小增加很多,運(yùn)行時(shí)還會(huì)產(chǎn)生額外的內(nèi)存占用,其可讀性也不高,因此官方強(qiáng)烈建議不要在Android程序里面使用到enum,枚舉單例缺點(diǎn)也很明顯。
7、容器實(shí)現(xiàn)單例模式
/**
* 單例容器類
*/
public class SingletonManager {
private SingletonManager() {
}
private static Map<String, Object> instanceMap = new HashMap<>();
public static void registerInstance(String key, Object instance) {
if (!instanceMap.containsKey(key)) {
instanceMap.put(key, instance);
}
}
public static Object getInstance(String key) {
return instanceMap.get(key);
}
}
/**
* 單例模板
*/
public class SingletonPattern {
SingletonPattern() {
}
public void doSomething() {
Log.d("==", "doSomeing");
}
}
// 具體用法
SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();
這種方式,根據(jù)key獲取對(duì)象對(duì)應(yīng)類型的對(duì)象,隱藏了具體實(shí)現(xiàn),降低了耦合度。