前言:單例模式是為了解決在程序中只能有一個的問題西剥,例如在我們的程序中經(jīng)常用到的線程池、緩存辛润、對話框和注冊表等對象永淌,都只需要實例化一個崎场,后面其他線程要用的時候都直接拿過來用即可。
案例分析:
REQ1:Vander接到這么個需求遂蛀,就是要創(chuàng)建一個糖果工廠來制造糖果谭跨,在糖果工廠放入蔗糖原料前必須保證熔爐是空的,接著在熔爐中煮沸并加入其它原料李滴,此時必須保證熔爐不是空的并且是沒有煮過的螃宙,最后將熔爐中的糖漿倒出到下一個機(jī)器進(jìn)行冷卻再加工。設(shè)計如下:
分析:首先這需要保證所坯,程序中有且僅有一個CandyBoiler谆扎,不然就可能會出現(xiàn)在沒有fill之前就直接boil了的情況,首先要保證能夠?qū)嵗荒芊旁陬愅饷媲壑钥梢酝ㄟ^將構(gòu)造方法私有化堂湖,于是代碼就是這么寫的:
public class CandyBoiler1 {
private boolean empty;
private boolean boiled;
private static CandyBoiler1 candyBoiler1;
private CandyBoiler1() {
System.out.println("_______________CandyBoiler Constructor____________");
empty = true;
boiled = false;
}
public static CandyBoiler1 getInstance() {
if(candyBoiler1 == null) {
candyBoiler1 = new CandyBoiler1();
}
return candyBoiler1;
}
public void fill() {
if(isEmpty()) {
System.out.println("___________fill material___________");
empty = false;
boiled = false;
}
}
public void boil() {
if((!isBoiled()) && (!isEmpty())) {
System.out.println("___________boil material___________");
empty = false;
boiled = true;
}
}
public void drain() {
if((isBoiled()) && (!isEmpty())) {
System.out.println("___________drain material___________");
//將糖漿倒出后恢復(fù)原狀態(tài)
empty = true;
boiled = false;
}
}
public void makeCandy() {
fill();
boil();
drain();
}
public boolean isEmpty() {
return empty;
}
public boolean isBoiled() {
return boiled;
}
}
說明:這么一來闲先,在實現(xiàn)的時候發(fā)現(xiàn),無法應(yīng)對多線程的情況:
public class Main {
public static void main(String[] args) {
CandyProducer1 pro1 = new CandyProducer1();
Thread thread1 = new Thread(pro1);
CandyProducer2 pro2 = new CandyProducer2();
Thread thread2 = new Thread(pro2);
CandyProducer3 pro3 = new CandyProducer3();
Thread thread3 = new Thread(pro3);
thread1.start();
thread2.start();
thread3.start();
}
}
結(jié)果:發(fā)現(xiàn)CandyBoiler被創(chuàng)建了3次无蜂,為什么呢伺糠?
說明:這是由于線程1剛開始肯定需要創(chuàng)建新的CandyBoiler,接著進(jìn)入構(gòu)造方法中斥季,打印CandyBoiler Constructor训桶,打印較為費時,此時線程2來了酣倾,線程2發(fā)現(xiàn)此時candyBoiler1為null舵揭,因為實際上此時線程1還在處理print語句,還沒有完成創(chuàng)建呢躁锡,這樣就導(dǎo)致了創(chuàng)建了兩個CandyBoiler午绳。
解決方法1:synchronized
Vander 又開始改進(jìn)設(shè)計了,Vander想起以前學(xué)過synchronized關(guān)鍵字稚铣,這樣還不簡單箱叁,每次只允許一個線程訪問getInstance方法,在getInstance方法加入synchronized關(guān)鍵字即可惕医。
重新修改getInstance方法:
public static synchronized CandyBoiler2 getInstance() {
if(candyBoiler2 == null) {
candyBoiler2 = new CandyBoiler2();
}
return candyBoiler2;
}
REQ2:接著Vander進(jìn)行測試,果然只創(chuàng)建了一個CandyBoiler對象算色,正當(dāng)Vander沾沾自喜抬伺,以為完美解決了問題的時候,Panda出現(xiàn)了灾梦,Panda說你這么寫你就不怕影響性能嗎峡钓?每次線程要來獲取CandyBoiler的時候,都需要等其它線程拿完了才能拿若河,這樣根本無法應(yīng)對高并發(fā)量的需求能岩。Vander一聽,難道還有別的解決方法嗎萧福?
解決方法2:使用“急切”創(chuàng)建實例**
Panda大師說這個問題其實還有兩種解決方法拉鹃,其實new CandyBoiler(),這句話只會運行一次鲫忍,其實也可以在剛開始加載CandyBoiler的時候膏燕,直接就把這個鍋爐對象new出來,后面全是拿來用就行了悟民。
public class CandyBoiler3 {
private boolean empty;
private boolean boiled;
private static CandyBoiler3 candyBoiler3 = new CandyBoiler3();
private CandyBoiler3() {
System.out.println("_______________CandyBoiler Constructor____________");
empty = true;
boiled = false;
}
public static CandyBoiler3 getInstance() {
return candyBoiler3;
}
… … … …
… … … …
}
說明:壞處:提前將實例創(chuàng)建好坝辫,而不是等到要用的時候才創(chuàng)建,如果應(yīng)用程序總是創(chuàng)建并使用單例射亏,或者在創(chuàng)建和運行時方面的負(fù)擔(dān)不太繁重近忙,可以在初始化這個類的時候就創(chuàng)建此單件竭业。
解決方法3:**使用“雙重檢查加鎖”,在getInstance()中減少使用同步及舍。
利用雙重檢查加鎖永品,首先檢查是否實例已經(jīng)創(chuàng)建,如果尚未創(chuàng)建才進(jìn)行同步击纬,這樣依賴就只有第一次會同步鼎姐。
public class CandyBoiler4 {
private boolean empty;
private boolean boiled;
private volatile static CandyBoiler4 candyBoiler4;
private CandyBoiler4() {
System.out.println("_______________new CandyBoiler()____________");
empty = true;
boiled = false;
}
public static CandyBoiler4 getInstance() {
if(candyBoiler4 == null) {
synchronized (CandyBoiler4.class) {
if(candyBoiler4 == null) {
candyBoiler4 = new CandyBoiler4();
}
}
}
return candyBoiler4;
}
… … … …
… … … …
}
最后的最后,我們又來總結(jié)我們現(xiàn)在現(xiàn)有的設(shè)計模式武器更振。
面向?qū)ο蠡A(chǔ)
抽象炕桨、封裝、多態(tài)肯腕、繼承
六大設(shè)計原則
設(shè)計原則一:封裝變化
設(shè)計原則二:針對接口編程献宫,不針對實現(xiàn)編程。
設(shè)計原則三:多用組合实撒,少用繼承姊途。
設(shè)計原則四:為交互對象之間的松耦合設(shè)計而努力
設(shè)計原則五:對擴(kuò)展開放,對修改關(guān)閉
設(shè)計原則六:依賴抽象知态,不要依賴于具體的類
模式
單例模式:確保一個類只有一個實例捷兰,并提供全局訪問點。