題目:設(shè)計(jì)一個(gè)類畦浓,我們只能生成該類的一個(gè)實(shí)例
題目其實(shí)說(shuō)到底只是常見(jiàn)的單例模式蜜猾,相信大家對(duì)這個(gè)最基礎(chǔ)的設(shè)計(jì)模式早已有所了解傀蚌。
解法一(不好浩姥,只適用于單線程環(huán)境)
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
這里所寫(xiě)的是一個(gè)典型的懶漢式單例散罕,還有一種餓漢式的單例模式分歇,會(huì)在下面介紹。這種寫(xiě)法之所以不好就是因?yàn)樗荒茉诙嗑€程的環(huán)境下使用欧漱。試想一下职抡,此時(shí)有兩個(gè)線程:線程1和線程2,其中線程1執(zhí)行到了if (instance == null)
這條語(yǔ)句误甚,這個(gè)時(shí)候線程2如果搶到執(zhí)行權(quán)并且一直執(zhí)行下去缚甩,線程1也就不得不一直等待。當(dāng)線程2執(zhí)行完畢并且返回了一個(gè)Singleton對(duì)象后線程1便開(kāi)始從instance = new Singleton();
這條語(yǔ)句開(kāi)始執(zhí)行窑邦,于是在線程2創(chuàng)建了一個(gè)對(duì)象之后擅威,線程1也創(chuàng)建了一個(gè)對(duì)象。因此冈钦,想要只創(chuàng)建一個(gè)對(duì)象的要求在這里是無(wú)法實(shí)現(xiàn)的郊丛。
解法二(雖然在多線程環(huán)境中能夠工作但是效率不高)
解法一的失敗之處主要是由于沒(méi)有考慮多線程的情況,而對(duì)于這種情況最直接的解決方法就是在會(huì)出現(xiàn)問(wèn)題的地方加上一個(gè)同步代碼快瞧筛。
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
return instance;
}
}
現(xiàn)在這個(gè)代碼已經(jīng)可以保證在多線程的情況下只會(huì)創(chuàng)建唯一的一個(gè)實(shí)例厉熟,成功地解決了我們?cè)诮夥ㄒ恢杏龅降膯?wèn)題。但是上鎖是一個(gè)對(duì)于程序來(lái)說(shuō)很耗時(shí)的操作较幌。而我們發(fā)現(xiàn)在這段代碼中無(wú)論什么情況下揍瑟,當(dāng)任何一個(gè)線程想要?jiǎng)?chuàng)建一個(gè)Singleton對(duì)象的時(shí)候都要上一次鎖。毫無(wú)疑問(wèn)乍炉,這大大地降低了程序的運(yùn)行效率绢片,因此還需改進(jìn)嘁字。
解法三(加同步鎖前后兩次判斷實(shí)例是否已經(jīng)存在)
對(duì)解法二中代碼的分析我們發(fā)現(xiàn),事實(shí)上以下代碼只在實(shí)例還沒(méi)有創(chuàng)建的時(shí)候執(zhí)行就行了杉畜。
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
于是為了避免在實(shí)例已經(jīng)創(chuàng)建的情況下纪蜒,某一線程想要獲取實(shí)例的時(shí)候仍然會(huì)去執(zhí)行這段同步代碼塊我們有了解法三。
public class Singleton {
private Singleton {}
private static Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
上面這段代碼在解法二的同步代碼塊之外又加上了一個(gè)對(duì)于實(shí)例是否已經(jīng)存在的判斷此叠。通過(guò)這種方法纯续,在實(shí)例已經(jīng)創(chuàng)建的情況下,任何線程在執(zhí)行過(guò)程中便不會(huì)再去執(zhí)行同步代碼灭袁,因此在解法二的基礎(chǔ)上大大地提高了程序的運(yùn)行效率猬错。
解法四(餓漢式解決線程問(wèn)題)
解法三看上去雖然已經(jīng)夠用,但是代碼內(nèi)容較長(zhǎng)茸歧。那么倦炒,有沒(méi)有不用同步便能夠?qū)崿F(xiàn)多線程情況下的單例模式的方法呢?餓漢式單例模式就是這個(gè)問(wèn)題的答案软瞎。
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton;
public static Singleton getInstance() {
return instance;
}
}
餓漢式單例模式巧妙地避免了上面三個(gè)解法圍繞的線程安全問(wèn)題逢唤。但是值得注意的是,實(shí)例instance
并不是第一次調(diào)用Singleton.getInstance()
的時(shí)候創(chuàng)建涤浇,而是程序中第一次用到Singleton
的時(shí)候就會(huì)被創(chuàng)建鳖藕。假設(shè)我們?cè)谏厦娴拇a中插入一段與單例要求無(wú)關(guān)的靜態(tài)函數(shù),當(dāng)我們使用這個(gè)靜態(tài)函數(shù)的時(shí)候本是沒(méi)由創(chuàng)建Singleton
實(shí)例的只锭,但是餓漢式單例仍然會(huì)為我們過(guò)早地創(chuàng)建實(shí)例著恩,從而降低內(nèi)存的使用效率。
解法五(實(shí)現(xiàn)按需創(chuàng)建實(shí)例)
針對(duì)解法四蜻展,這里的解法五很好地解決了這個(gè)問(wèn)題喉誊。
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return Nested.instance;
}
private static class Nested {
static Singleton instance = new Singleton();
}
}
這段代碼中我們?cè)?code>Singleton中定義了一個(gè)私有類型Nested
,該類型只有在調(diào)用Singleton.getInstance()
的時(shí)候才會(huì)被調(diào)用纵顾。由于我們將Nested
定義為private
類型伍茄,其他類無(wú)法調(diào)用Nested
類型。因此片挂,Singleton實(shí)例只有在任何情況下只有我們調(diào)用Singleton.getInstance()
的時(shí)候才會(huì)被創(chuàng)建幻林,最終很好地解決了解法四中的提前創(chuàng)建實(shí)例的問(wèn)題贞盯,真正地做到了按需創(chuàng)建音念。