設(shè)計模式雜談(1)單例模式。【使用頻率:99.999%,學(xué)習(xí)難度:0.001%】

作者:solo陳,轉(zhuǎn)載請注明出處。
個人主頁:http://m.itdecent.cn/users/5c2177416a84/latest_articles
??設(shè)計模式是你在學(xué)習(xí)java道路上必須要學(xué)會掌握的,當(dāng)然也并不是24種設(shè)計模式你都要掌握得很透徹。下面列出幾項:《設(shè)計模式的好處》
一、有助于設(shè)計系統(tǒng)架構(gòu)、增強系統(tǒng)健壯性、利于維護(hù)
二、有利于加強思考問題的思維、思考能力
三、有利于自己的編寫代碼、閱讀代碼的能力
??當(dāng)然你在編碼設(shè)計過程中如果只是為了顯擺一下你會某種設(shè)計模式這個是沒必要的,因為過多的使用并不會給程序帶來良好的閱讀性,反而會增加系統(tǒng)的復(fù)雜度,所以應(yīng)該因地制宜。其實如何在正確的地方正確的使用設(shè)計模式是初學(xué)者普遍會問的問題。比如LZ當(dāng)時就會有這個的問題:學(xué)了單例模式,自己也會寫,但是在什么情況下使用呢?這個就不得而知了,導(dǎo)致自己很多時候都很迷茫自己究竟有沒有學(xué)習(xí)設(shè)計模式的必要。所以LZ寫這個文章的初衷是將工作中使用到的結(jié)合起來進(jìn)行說明:
??下面先來說說單例模式。單例模式不論在自己編碼或者源代碼中都會遇見,所以此模式是你學(xué)習(xí)時候必須掌握的。該在什么時候使用呢?LZ在學(xué)習(xí)中也常常問自己,單例模式都有一個共性就是
1、這個類沒有狀態(tài)
??有狀態(tài)的類:類里面有成員變量,而且成員變量是可變的、比如struts2的action、要求是多例的、因為他是有狀態(tài)的
??無狀態(tài)類:類里面沒有成員變量,或者有成員變量但是不可變的、或者成員變量是單例的、比如struts1的action、可以是單例的。因為他是沒有狀態(tài)的
2、比如:工作上類似你一個AppServer類作為啟動資源類并進(jìn)行各種初始化工作,那么你的這個類就可以寫出單例,因為這個類在使用的過程中并不需要每次都進(jìn)行new來實例對象,我們只需要單獨的一份就可以了。
說白了單例就是你new無數(shù)個其實都是一樣的,并且在邏輯上如果new多個也會發(fā)生邏輯錯誤。

在網(wǎng)上該模式又分為懶漢、餓漢等幾種形式。LZ就不進(jìn)行細(xì)分了,因為LZ并不想在學(xué)習(xí)單例模式的時候再對文字進(jìn)行理解記憶。下面就是幾種逐漸進(jìn)化的單例模式:
一、這種也是學(xué)習(xí)設(shè)計模式時老師講解的最初級版本

//這是在不考慮并發(fā)訪問的情況下標(biāo)準(zhǔn)的單例模式的構(gòu)造方式,這種方式通過幾個地方來限制了我們?nèi)〉降膶嵗俏ㄒ坏摹?public class Singleton {    
  private Singleton(){}    
  private static Singleton singleton ;    
  public static Singleton getInstance (){          
        if(singleton == null){                
              singleton = new Singleton();           
        }          
        return singleton ;      
  }
}

這種寫法是在初學(xué)是不考慮并發(fā)情況的構(gòu)造方式,通過幾點來確定獲取唯一的實例:
1、使用private權(quán)限的構(gòu)造器,使得客戶端(使用者)不能夠隨意創(chuàng)建對象
2、使用static關(guān)鍵字來使得屬性所指向的對象在每一個類中都是唯一的
3、static方法,使得客戶端可以直接通過類.方法名調(diào)用。如果沒有static就會使得客戶端無法獲取實例進(jìn)行調(diào)用
??上面的方法屬于大學(xué)畢業(yè)階段的代碼,因為他并沒有考慮到如果在多線程環(huán)境會造成的影響,如果多個線程(A\B)同時來訪問getInstance這個方法,那么在if判斷這里A線程判斷為空,然而B線程正好在執(zhí)行singleton = new Singleton(); 創(chuàng)建實例方法,但是并沒有真正實例出對象,那么A線程也會繼續(xù)執(zhí)行singleton = new Singleton(); 創(chuàng)建實例方法。從而導(dǎo)致會創(chuàng)建多個實例。

二、有人說第一種是沒考慮多線程那么就加鎖

/*此種加鎖方式會導(dǎo)致運行速度降低,當(dāng)一個線程進(jìn)行訪問的時候,其余所有線程都將掛起等待
*/
public class StupidSynchronizedSingleton {
    private StupidSynchronizedSingleton (){}
    private static StupidSynchronizedSingleton badsingleton ; 

    public synchronized static StupidSynchronizedSingleton getInstance (){
        if(badsingleton == null){
            badsingleton = new StupidSynchronizedSingleton();
        }
        return badsingleton ;
    }
}

由于該方法的做法實在是太愚蠢了,所以LZ給它取名Stupid。上面的做法是將getInstance ()進(jìn)行同步來解決多線程問題,但是當(dāng)訪問getInstance ()時,其余的線程會進(jìn)行掛起,造成無謂的等待,顯然這種等待是沒有必要的。

三、在第二種方法上適當(dāng)?shù)男薷木涂梢越鉀Q那種無謂的等待了。

//這種實現(xiàn)方式實現(xiàn)了雙重鎖機制
/**
 *  首先要明白在JVM創(chuàng)建新的對象時,主要要經(jīng)過三步。
              1.分配內(nèi)存
              2.初始化構(gòu)造器
              3.將對象指向分配的內(nèi)存的地址
              但是在new實例的時候有可能是先將對象分配給內(nèi)存,在初始化。這個時候返回的synsingleton就會出現(xiàn)未知的錯誤
 *
 */

public class SynchronizedSingleton {
    private SynchronizedSingleton (){}
    private static SynchronizedSingleton synsingleton ; 
    public static SynchronizedSingleton getInstance (){
        if(synsingleton == null){//1
            synchronized (SynchronizedSingleton.class) {
                if(synsingleton == null){//2
                    synsingleton = new SynchronizedSingleton();
                }
            }
        }
        return synsingleton ;
    }
}

上面方法相比較第二種方法做的同步就要正確得多了,并沒有在方法上直接進(jìn)行同步,而是判斷了變量是否為null之后再進(jìn)行同步,否則就直接進(jìn)行返回,從而減少了在有實例情況下的等待時間。
假設(shè)synsingleton為null(1),此時有A/B線程同時執(zhí)行到synchronized塊,假設(shè)A線程搶占到資源再次執(zhí)行synsingleton為null(2)當(dāng)判斷為true時,就進(jìn)行實例化對象,否則返回。B之后進(jìn)入同步塊是同樣。再次執(zhí)行判斷的原因是確保多個線程進(jìn)入注釋1后代碼的邏輯正確性。從上面代碼中可以看到有兩次判斷是否為null,這就是所謂的雙重鎖機制。
上面代碼從表面上看是沒有任何問題的,但是如果你了解JVM創(chuàng)建對象的邏輯步驟你就會發(fā)現(xiàn)上面做法也會出現(xiàn)問題。具體造成問題的原因看代碼上面的注釋。所以為了避免我們在創(chuàng)建對象時發(fā)生的此種問題,我們最好是交給JVM進(jìn)行。

四、將創(chuàng)建對象的時機將給JVM加載類的時候進(jìn)行(類加載這里就不詳細(xì)說明了,有時間單獨寫一個我對類加載過程的理解的一篇文章)

/*屬性為static的會在類加載的時候初始化,所以在初始化進(jìn)行一半的時候,別的線程      *是無法使用的,這個是jvm保證的
*/
public class InnerSingleton {
    private InnerSingleton (){};
    public static InnerSingleton getInstance (){
        return SingletonInstance.singleton;
    }
    private static class SingletonInstance {
        private static final InnerSingleton singleton = new InnerSingleton();
    }
}

首先static的成員變量會在類加載的時候進(jìn)行初始化,所以singleton在代碼調(diào)用之前就已經(jīng)實例化好了。

五、枚舉量來實現(xiàn)單例模式:
1、 自由序列化;
2、 保證只有一個實例(即使使用反射機制也無法多次實例化一個枚舉量);
3、 線程安全;

public class EnumSingleton {
    private EnumSingleton() {}
    
    private enum InstanceHolder {
        INSTANCE;
        private EnumSingleton value;

        private InstanceHolder() {
            value = new EnumSingleton();
        }
    }
    
    public static EnumSingleton getInstance() {
        return InstanceHolder.INSTANCE.value;
    }
}

枚舉形式不僅能避免多線程同步問題,而且還能防止反序列化重新創(chuàng)建新的對象,但是枚舉是1.5版本之后的新特性,所以這種方式很少用到

以上就是java單例模式的各種寫法,當(dāng)然在實際運用中你可以使用四、五方法來創(chuàng)建,如果是應(yīng)聘,面試官叫你寫單例模式你可以都寫出來然后講出各自的優(yōu)缺點,這樣相信你對單例模式的掌握已經(jīng)熟練了。
上面就是LZ對單例模式的理解,感謝各位的收看。這篇文章也是LZ在簡書上寫的第一篇,后續(xù)也會繼續(xù)分享自己對Java知識的理解,當(dāng)然LZ并不是什么大牛,也是在不斷的學(xué)習(xí)過程中分享自己理解,有什么問題可以在文章下發(fā)留言進(jìn)行交流。有錯的地方LZ也會改正。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容