一個(gè)類只有一個(gè)實(shí)例池凄,自行實(shí)例化并提供給整個(gè)系統(tǒng)枣察。
將該類構(gòu)造函數(shù)私有化魄眉,并通過(guò)靜態(tài)方法獲取一個(gè)唯一實(shí)例砰盐,獲取過(guò)程保證線程安全。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) { //分水嶺
instance = new Singleton();
}
return instance;
}
}
假設(shè)有A坑律、B線程同時(shí)調(diào)用getInstance方法岩梳,在分水嶺同時(shí)執(zhí)行完判斷后,由于CPU時(shí)間片切換晃择,假設(shè)A線程得到執(zhí)行權(quán)冀值,繼續(xù)向下執(zhí)行,實(shí)例化了對(duì)象宫屠,此時(shí)instance不為空,而B線程又做了實(shí)例化工作,就會(huì)導(dǎo)致實(shí)例不唯一乌逐。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在getInstance()方法前加上synchronized浙踢,使整個(gè)方法同步洛波,就可以保證線程安全,避免多實(shí)例的問(wèn)題缚窿,但是這樣并不高效倦零,因?yàn)樵诘谝淮握{(diào)用過(guò)getInstance()方法后扫茅,instance不為空育瓜,之后再調(diào)用該方法都要走同步躏仇,這樣會(huì)消耗不必要的資源。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {? ? ? ? ? ? ? ? ? ? ? ? //Single Checked
synchronized (Singleton.class) {
if (instance == null) {? ? ? ? ? ? ? ? //Double Checked
instance = new Singleton();
}
}
}
return instance ;
}
}
這里為啥要做兩次檢驗(yàn)?zāi)鼗燮穑课覀兿葋?lái)看下册倒,如果去掉第一層檢驗(yàn)會(huì)怎樣
public static Singleton getSingleton() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
return instance ;
}
當(dāng)有兩個(gè)線程同時(shí)調(diào)用getSingleton()方法時(shí),由于同步機(jī)制的存在灿意,假設(shè)此時(shí)A線程得到CPU的執(zhí)行權(quán)崇呵,走到同步塊中執(zhí)行域慷,執(zhí)行了instance = new Singleton()后汗销,退出同步語(yǔ)句塊弛针,此時(shí)instance已經(jīng)不為null了削茁,這時(shí)候B線程獲得CPU執(zhí)行權(quán)也進(jìn)入同步塊茧跋,就會(huì)被instance == null的判斷擋在外面了瘾杭。
所以就算不要第一層檢驗(yàn)也是可以實(shí)現(xiàn)多線程安全的單例捍岳,那為毛還要寫锣夹?原來(lái)這里涉及到性能問(wèn)題,上面懶漢式線程安全的寫法也是安全的变勇,但是每次調(diào)用都要走同步會(huì)影響性能搀绣,這里也一樣链患。我們希望這里的new Singleton()只執(zhí)行一次,如果少了第一層的判斷瓶您,每次有線程進(jìn)入 getInstance()時(shí),均會(huì)執(zhí)行鎖定操作來(lái)實(shí)現(xiàn)線程同步呀袱,這是非常耗費(fèi)性能的贸毕,而多了第一層檢驗(yàn)夜赵,只有第一次instance == null會(huì)執(zhí)行鎖定操作明棍,之后的調(diào)用直接return instance就行寇僧。
再看如果少了第二層檢驗(yàn)摊腋,其實(shí)這就跟第一種懶漢式線程不安全寫法一樣沸版,在多線程并發(fā)的情況下不能達(dá)到保持單例的效果。
那么雙重檢驗(yàn)機(jī)制是否就能完美解決問(wèn)題推穷?其實(shí)不然,其原因在于instance = new Singleton();這句并非是個(gè)原子操作蟹腾,當(dāng)JVM運(yùn)行到這一句是,分別作了如下操作
1.在堆中為 instance 分配內(nèi)存
2.調(diào)用 Singleton的構(gòu)造函數(shù)初始化成員變量
3.將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance 就為非 null 了)
如果上面的操作按1-2-3順序執(zhí)行,那就沒(méi)啥問(wèn)題炉爆,但是由于JVM的即時(shí)編譯器中存在指令重排序的優(yōu)化,上面的操作有可能是1-3-2。這會(huì)出現(xiàn)啥情況郁稍?
public static Singleton getSingleton() {
if (instance == null) {? ? ? ? //p2
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); //p1
}
}
}
return instance ;
}
假設(shè)線程A此時(shí)走到p1處赦政,JVM按照1-3-2的順序?qū)嵗痠nstance,才執(zhí)行完1-3,來(lái)不及執(zhí)行2耀怜,線程B獲得了CPU執(zhí)行權(quán)也調(diào)用了getInstance()方法恢着,并走到p2處,此時(shí)instance不為null,線程B就屁顛屁顛拿著得到的實(shí)例回去干活了财破,但是里頭的成員變量還沒(méi)初始化掰派,這就尷尬了,報(bào)錯(cuò)也就理所當(dāng)然了左痢。
要解決這問(wèn)題只要把instance設(shè)置成volatile靡羡,禁止重排序優(yōu)化,以上面的例子抖锥,不管是1-2-3還是1-3-2亿眠,讀取必須在操作完全執(zhí)行完后才能進(jìn)行。
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getSingleton() {
if (instance == null) {? ? ? ? ? ? ? ? ? ? ? ? //Single Checked
synchronized (Singleton.class) {
if (instance == null) {? ? ? ? ? ? ? ? //Double Checked
instance = new Singleton();
}
}
}
return instance ;
}
}
然而在Java5以前的版本使用volatile還是有問(wèn)題磅废,也不能完全避免重排序纳像。
public class Singleton{
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
由于instance被static和final修飾,在該類第一次被加載到內(nèi)存中時(shí)就會(huì)對(duì)instance實(shí)例化拯勉,這樣保證了單例竟趾。缺點(diǎn)是就算其他地方?jīng)]有調(diào)用getInstance()方法憔购,也會(huì)創(chuàng)建實(shí)例。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
靜態(tài)內(nèi)部類就比較好的彌補(bǔ)了惡漢式單例不足岔帽,也是比較推崇的寫法玫鸟。
public enum EnumSingleton{
INSTANCE;
}
創(chuàng)建枚舉默認(rèn)就是線程安全的,所以不需要擔(dān)心線程同步犀勒,而且還能防止反序列化導(dǎo)致重新創(chuàng)建新的對(duì)象屎飘。通過(guò)EnumSingleton.INSTANCE來(lái)訪問(wèn)實(shí)例,只是平時(shí)很少看到代碼中有人這么寫贾费。
本文作者:HuYounger
本文標(biāo)題:Java單例模式
本文鏈接:http://rkhcy.github.io/2017/03/29/Java單例模式/
發(fā)布時(shí)間:2017年3月29日 - 00時(shí)03分
版權(quán)聲明:本文由 HuYounger 原創(chuàng)钦购,采用保留署名-非商業(yè)性使用-禁止演繹 4.0-國(guó)際許可協(xié)議
轉(zhuǎn)載請(qǐng)保留以上聲明信息!