單例模式
單例模式幾乎是最簡(jiǎn)單的模式了
public class Singleton {
private Singleton(){};
private static Singleton singleton = null;
public static Singleton getInstance()
{
if(singleton == null)
{
singleton = new Singleton();
}
return singleton;
}
}
私有化構(gòu)造方法富弦。
靜態(tài)的Singleton singleton和getInstance()方法茫舶。
但是上面這種做法是線程不安全的,會(huì)造成數(shù)據(jù)污染因宇。
改進(jìn)如下DCL(Double Check Lock)
:
public class Singleton {
private Singleton(){};
private static volatile Singleton singleton = null;
public static Singleton getInstance(){
if(singleton == null)
{
synchronized (Singleton.class)
{
if(singleton == null)
{
singleton = new Singleton();
}
}
}
return singleton;
}
}
volatile 關(guān)鍵字有和 synchronized 相當(dāng)?shù)男Ч婕簦情_銷更小,修飾變量彩郊。
synchronized 修飾方法或方法塊前弯。具體參考下Java 并發(fā)編程:volatile的使用及其原理getInstance()方法中需要使用同步鎖synchronized (Singleton.class)防止多線程同時(shí)進(jìn)入造成instance被多次實(shí)例化
可以看到上面在synchronized (Singleton.class)外又添加了一層if,這是為了在instance已經(jīng)實(shí)例化后下次進(jìn)入不必執(zhí)行synchronized (Singleton.class)獲取對(duì)象鎖秫逝,從而提高性能恕出。
總體上來(lái)說(shuō)volatile的理解還是比較困難的,如果不是特別理解违帆,也不用急浙巫,完全理解需要一個(gè)過(guò)程,在后續(xù)的文章中也還會(huì)多次看到volatile的使用場(chǎng)景刷后。這里暫且對(duì)volatile的基礎(chǔ)知識(shí)和原來(lái)有一個(gè)基本的了解的畴。總體來(lái)說(shuō)尝胆,volatile是并發(fā)編程中的一種優(yōu)化丧裁,在某些場(chǎng)景下可以代替Synchronized。但是含衔,volatile的不能完全取代Synchronized的位置煎娇,只有在一些特殊的場(chǎng)景下,才能適用volatile贪染』呵海總的來(lái)說(shuō),必須同時(shí)滿足下面兩個(gè)條件才能保證在并發(fā)環(huán)境的線程安全:
(1)對(duì)變量的寫操作不依賴于當(dāng)前值杭隙。
(2)該變量沒(méi)有包含在具有其他變量的不變式中哟绊。
說(shuō)的好復(fù)雜,說(shuō)點(diǎn)簡(jiǎn)單的寺渗,下面摘錄于《設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
上面代碼這樣定義singleton
,不添加volatile
關(guān)鍵字時(shí):
private static Singleton singleton = null;
假設(shè)線程A執(zhí)行到singleton = new Singleton();
語(yǔ)句匿情,這里看起來(lái)是一句代碼兰迫,但實(shí)際上它并不是一個(gè)原子操作,這句代碼最終會(huì)編譯成多條匯編指令炬称,它大致做了3件事情汁果。
(1)給Singleton
的實(shí)例分配內(nèi)存
(2)調(diào)用Singleton()
的構(gòu)造函數(shù),初始化成員字段玲躯。
(3)將singleton
對(duì)象指向分配的內(nèi)存空間(此時(shí)singleton
就不是null
了)
??但是据德,由于Java
編譯器允許處理器亂序執(zhí)行,以及JDK1.5
之前JMM(Java Memory Model, 即Java內(nèi)存模型)
中Cache
跷车、寄存器到主內(nèi)存回寫順序的規(guī)定棘利,上面的第二和第三的順序是無(wú)法保證的,也就是說(shuō)朽缴,執(zhí)行順序可能是1-2-3
也可能是1-3-2
善玫,如果是后者,并且在3執(zhí)行完畢密强,2未執(zhí)行之前茅郎,被切換到線程B上,這時(shí)候singleton
因?yàn)橐呀?jīng)在線程A內(nèi)執(zhí)行過(guò)第三點(diǎn)或渤,singleton
已經(jīng)是非空了系冗,所以,線程B
直接取走singleton
薪鹦,再使用時(shí)就會(huì)出錯(cuò)掌敬,這就是DCL(Double Check Lock)
失效問(wèn)題,而且這種難以追蹤難以重現(xiàn)
的錯(cuò)誤可能會(huì)影響很久池磁。
??在JDK1.5
之后奔害,SUN
官方已經(jīng)注意到這種問(wèn)題,調(diào)整了JVM
,具體化了volatile
關(guān)鍵字框仔,因此舀武,如果JDK
是1.5或之后的版本,只需要將singleton
的定義改成private volatile static Singleton singleton = null;
就可以保證singleton
對(duì)象每次都是從主內(nèi)存中讀取离斩,就可以使用DCL
的寫法來(lái)完成單例模式银舱。當(dāng)然,volatile或多或少也會(huì)影響到性能跛梗,但考慮到程序的正確性寻馏,犧牲這點(diǎn)性能還是值得的。
內(nèi)部類單例模式
??DCL雖然在一定程度上解決了資源消耗核偿、多余的同步诚欠、線程安全等問(wèn)題,但是它還是在某些情況下出現(xiàn)了失效的問(wèn)題,這個(gè)問(wèn)題被稱為雙重檢查鎖定(DCL)
失效轰绵,在《Java并發(fā)編程實(shí)踐》
一書的最后談到了這個(gè)問(wèn)題粉寞,并指出這種“優(yōu)化”是丑陋的,不贊成使用左腔。而建議使用如下的代碼替換唧垦。
public class Singleton {
private Singleton(){}
public static Singleton getInstance(){
return SingletonHolder.sInstence;
}
/**
* 靜態(tài)內(nèi)部類
*/
private static class SingletonHolder{
private static final Singleton sInstence = new Singleton();
}
}
&emsp&emsp當(dāng)?shù)谝淮渭虞dSingleton
類時(shí)并不會(huì)初始化sInstence
,只有在第一次調(diào)用Singleton
的getInstance()
方法時(shí)才導(dǎo)致sInstence
被初始化,因此液样,第一次調(diào)用getInstance()
方法會(huì)導(dǎo)致虛擬機(jī)加載SingletonHodler
類,這種方式不僅能夠確保線程安全振亮,也能夠保證單例對(duì)象的唯一性,同時(shí)也延遲了單例的實(shí)例化鞭莽,所以這是推薦使用的單例模式實(shí)現(xiàn)方式坊秸。