Java設(shè)計(jì)模式——單例模式
1行拢、餓漢模式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton newInstance() {
return instance;
}
}
將對象構(gòu)建放在了static語句中氮兵,JVM加載類的時候會執(zhí)行靜態(tài)語句和靜態(tài)代碼塊荠耽,而且虛擬機(jī)保證類只被初始化一次眶根,所以類的初始化是單線程執(zhí)行的腥放,所以這種是線程安全的泛啸,缺點(diǎn)就是初始化太早,可能造成空間浪費(fèi)秃症。
2平痰、懶漢模式,延遲加載
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在加載類的時候不會進(jìn)行實(shí)例化伍纫,在獲取實(shí)例的話宗雇,如果為空,則進(jìn)行實(shí)例化莹规,單線程不會出問題赔蒲,多線程可能會創(chuàng)建多個實(shí)例,造成不是單例良漱,可以進(jìn)行改進(jìn):
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton newInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
使用synchronized
控制線程同步訪問
3舞虱、雙重校驗(yàn)鎖
上述模式實(shí)現(xiàn)了延遲加載和線程安全,但是由于使用synchronized
會造成性能下降母市,因?yàn)橥瑫r只有一個進(jìn)程可以調(diào)用newInstance()
方法矾兜,采用雙重校驗(yàn)鎖可以提高性能
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
synchronized
并沒有加在方法上,一般情況下患久,當(dāng)倆個線程執(zhí)行newInstance()
方法椅寺,判斷instance
都為null
時,就會創(chuàng)建倆個不同的實(shí)例蒋失,所以為了解決這個問題返帕,所以在同步代碼塊中還得加入驗(yàn)證instance == null
代碼。
這里還有一個問題篙挽,由于Java的指令重排優(yōu)化的存在荆萤,instance = new Singleton()
語句不具有原子性,他是分幾步執(zhí)行的:
1.給對象分配內(nèi)存空間
2.在內(nèi)存空間創(chuàng)建對象
3.將對象引用賦值給instance
2必須在1之后執(zhí)行铣卡,但是3和2之間沒有依賴關(guān)系链韭,程序執(zhí)行的時候順序可以是1->2->3,也可能是1->3->2
如果是單線程程序煮落,無論執(zhí)行的順序是什么敞峭,使用instances
的時候,虛擬機(jī)可以保證它是初始化完成的
如果是多線程程序州邢,一個線程在執(zhí)行instance = new Singleton()
的時候儡陨,其他的線程是要執(zhí)行第一個if (instance == null)
語句的褪子,因?yàn)檫@個語句在同步鎖的外面,這個時候就涉及到執(zhí)行順序:
如果按1->2->3執(zhí)行骗村,在執(zhí)行3之前嫌褪,instance
的引用一直都是null
,如果這時候第二個線程執(zhí)行newInstance()
方法胚股,就會一直在同步代碼塊外面等待笼痛,直到同步代碼塊執(zhí)行完3后,第二個線程進(jìn)入琅拌,判斷instance
不為空缨伊,然后執(zhí)行return語句,返回的是第一個線程初始化的對象进宝,這是正確的刻坊。
如果按1->3->2執(zhí)行,執(zhí)行完3但是還沒有執(zhí)行2的時候党晋,第二個線程執(zhí)行newInstance()
方法谭胚,由于現(xiàn)在的instance
已經(jīng)有了引用對象,不是null
未玻,所有第二個線程不會在同步代碼塊外面等待灾而,會直接執(zhí)行return語句,此時返回的instance
還沒有完成初始化扳剿,是錯誤的對象旁趟。
上面的問題就是因?yàn)镴VM的自動優(yōu)化操作,可以使用volatile
禁止指令重排優(yōu)化庇绽,也就是保證先分配空間锡搜,然后賦值引用,代碼如下:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton newInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
4敛劝、靜態(tài)內(nèi)部類
public class Singleton {
private static class SingletonHolder {
public static Singleton instance = new Singleton();
}
private Singleton() {}
public static Singleton newInstance() {
return SingletonHolder.instance;
}
}
不會在一開始就加載靜態(tài)內(nèi)部類SingletonHolder
余爆,其他地方使用SingletonHolder
的時候才會加載,比如第一次調(diào)用newInstance()
方法夸盟,從而實(shí)現(xiàn)了延遲加載。和餓漢模式一樣像捶,也利用了類加載機(jī)制上陕,所以可以保證線程安全。
5拓春、枚舉
public enum Singleton {
INSTANCE;
}