單例模式(新手推薦)

平時(shí)上班比較忙,我就周末給大家更新下。今天給大家?guī)?lái)的是——單例模式。這個(gè)模式相對(duì)來(lái)說(shuō)比較簡(jiǎn)單一點(diǎn),但是有幾個(gè)點(diǎn)還是得說(shuō)一下,我們可以將這些細(xì)節(jié)用到我們自己的程序中。本來(lái)不想講的,但是我想把23種設(shè)計(jì)模式做成一個(gè)專題,以后大家不管是復(fù)習(xí)還是面試等,都可以有個(gè)參考。雖然寫(xiě)得并不好。

單例模式,聽(tīng)名字就能感覺(jué)出來(lái),就是只能有一個(gè)實(shí)例存在。的確如此,你的感覺(jué)很正確。既然只能存在一個(gè)實(shí)例,那么這個(gè)實(shí)例在什么時(shí)候創(chuàng)建呢?問(wèn)得好,我們看一下單例模式最常用的兩種分類。

一 懶漢式

lanhan
lanhan

用什么能表達(dá)我對(duì)你的心?唯有代碼,看下來(lái)。

public class SingleTask {

public static SingleTask mSingleTask = null;

private SingleTask(){};

public static SingleTask getNewInstance(){
    
    if(null == mSingleTask){
        mSingleTask = new SingleTask();
    }
    return mSingleTask;
}

}

二 餓漢式

public class SingleTask1 {

private static SingleTask1 mSingleTask1 = new SingleTask1();

private SingleTask1(){}

public static SingleTask1 getNewInstance(){
    
    return mSingleTask1;
}

}

看吧,代碼是不是很簡(jiǎn)單。首先我們來(lái)看下什么是懶漢式,什么又是餓漢式。

懶漢式:顧名思義,很懶。不到最后一刻就不動(dòng)。在我們代碼里面就是到你用我的時(shí)候,我才創(chuàng)建,不用的時(shí)候,我就歇著。
餓漢式:顧名思義,饑渴。呸,換句話說(shuō),就是勤快吧。我在類初始化的時(shí)候就做好準(zhǔn)備,時(shí)刻迎接著使用者。

不管是,懶漢式還是餓漢式。通過(guò)代碼,我們都會(huì)發(fā)現(xiàn)一個(gè)特點(diǎn)。

  1. 在單例的類中,都會(huì)持有一個(gè)當(dāng)前類的對(duì)象;
  2. 構(gòu)造函數(shù)私有化;
  3. 提供一個(gè)訪問(wèn)該對(duì)象的靜態(tài)方法。

這么理解呢?

我們可以將單例模式視為————壟斷。對(duì),沒(méi)錯(cuò)就是壟斷。我不會(huì)讓你通過(guò)其他方法創(chuàng)建我,只能使用我自己創(chuàng)建的,所以私有化構(gòu)造函數(shù)。既然只有我能創(chuàng)建,那么我會(huì)為你提供一個(gè)方法,讓你通過(guò)合法途徑得到這個(gè)對(duì)象,而不是通過(guò)歪門(mén)邪道。

好了,現(xiàn)在我們來(lái)看下不同的地方。

首先餓漢式?jīng)]啥說(shuō)的,我們忽略。主要看一下懶漢式的代碼。

public static SingleTask getNewInstance(){
    
    if(null == mSingleTask){
        mSingleTask = new SingleTask();
    }
    return mSingleTask;
}

我們這么做,會(huì)不會(huì)有什么問(wèn)題?
問(wèn)題當(dāng)然會(huì)有,在單線程模式下當(dāng)然不會(huì)有問(wèn)題。在實(shí)際的問(wèn)題中,我們可能就不是單線程這么簡(jiǎn)單了,往往會(huì)涉及到多線程。在多線程的時(shí)候就會(huì)很容易出現(xiàn)問(wèn)題,會(huì)導(dǎo)致對(duì)象被重復(fù)創(chuàng)建,顯然,違背了設(shè)計(jì)單例的初衷。那么怎么解決呢?

等等,這個(gè)地方我不知道大家有沒(méi)有疑問(wèn)?多線程真的會(huì)出問(wèn)題嗎?(因?yàn)槲业牟┛偷亩ㄎ痪褪切率?,所以為了解除大家的疑?wèn),我寫(xiě)個(gè)代碼驗(yàn)證下,消除大家的疑慮)。

懶漢式的困擾一

public class SingleTask {

public static SingleTask mSingleTask = null;
private static int time = 0;

private SingleTask(){
    System.out.println(time++);
};

public static SingleTask getNewInstance(){
    
    if(null == mSingleTask){
        mSingleTask = new SingleTask();
    }
    return mSingleTask;
}

public static void main(String[] args){
    
    for(int i=0;i<100;i++){
        
        new Thread(){
            
            @Override
            public void run(){
                
                SingleTask.getNewInstance();
            }
        }.start();
    }
}
}

我在構(gòu)造函數(shù)中,打印一句話,以此來(lái)證明確實(shí)被創(chuàng)建多次了。

讓我們看看結(jié)果:

singleinstance1

singleinstance2

singleinstance3

我隨便運(yùn)行幾次,就已經(jīng)這樣了。證明代碼確實(shí)是有問(wèn)題的。那么如何改進(jìn)呢?

有人肯定會(huì)說(shuō),我知道,加鎖。好的,我們加鎖。

public static SingleTask getNewInstance() {

    synchronized (SingleTask.class) {
        if (null == mSingleTask) {

            mSingleTask = new SingleTask();
        }
    }

    return mSingleTask;
}
// 其他部分代碼不變的

來(lái)我們先運(yùn)行下,用事實(shí)說(shuō)話。

singleinstance4

運(yùn)行到你手困,結(jié)果只是調(diào)用一次。啊,好高興啊,終于解決了。我們來(lái)分析下這段代碼?

singleinstance5

假設(shè)現(xiàn)在有三個(gè)饑渴的線程A,B,C來(lái)拿東西了。想象一下畫(huà)面有助于增強(qiáng)印象。他們幾乎同時(shí)來(lái)到位置1??吹介T(mén)上有一把鎖,所以只能放一個(gè)進(jìn)去。他們商量了下,B進(jìn)去了(實(shí)際上誰(shuí)進(jìn)去并不確定)。B來(lái)到位置2,發(fā)現(xiàn)mSingleTask = null,所以來(lái)到位置3,創(chuàng)建對(duì)象咯。創(chuàng)建完走出去了。緊接著A,C兩個(gè)商量了下,C進(jìn)去了。發(fā)現(xiàn)mSingleTask已經(jīng)不為空了,很高興直接拿著跑了。最后剩下A,等待好久終于進(jìn)去了,和C一樣,偷完就跑真刺激。

問(wèn)題是解決了,本著處女座的原則。我們繼續(xù)分析下代碼看有沒(méi)有可以升華革命友誼的地方。

懶漢式的困擾二

既然是鎖,那么肯定會(huì)帶來(lái)一定的開(kāi)銷(xiāo),我們每次來(lái)的時(shí)候都要加鎖,這個(gè)代價(jià)可想而知。那么能有什么解決辦法嗎?
當(dāng)然,我是誰(shuí),我是不一樣的煙火。

public static SingleTask getNewInstance() {

    if (null == mSingleTask) {
        synchronized (SingleTask.class) {

            if (null == mSingleTask) {

                mSingleTask = new SingleTask();
            }
        }
    }
    return mSingleTask;
}

可以看到,我們加了雙重判空。這樣,只有在mSingleTask = null的情況下才會(huì)加鎖,然后苦苦等待對(duì)象創(chuàng)建完成。好了吧,當(dāng)mSingleTask被賦予了新的生命后就不需要再次加鎖。

三 靜態(tài)內(nèi)部類

public class SingleTask2 {

private static SingleTask2 mSingleTask2;
private SingleTask2(){
    //System.out.println("new instance");
}

public static SingleTask2 getSingleInstance() {

    return SingleTaskHolder.instance;
}


static class SingleTaskHolder {
    
    // 內(nèi)部類初始化單例實(shí)例
    private static SingleTask2 instance = new SingleTask2();

}

// 驗(yàn)證下
public static void main(String[] args){
    
    for(int i=0;i<100;i++){
        new Thread(){
            
            @Override
            public void run(){
                SingleTask2.getSingleInstance();
            }
        }.start();
        
    }    
}
}

通過(guò)這種方式我們也能實(shí)現(xiàn)單例模式,那么這么做的原理是什么呢?
因?yàn)殪o態(tài)內(nèi)部類只會(huì)被加載一次,所以這么做也是線程安全的。

好了,聽(tīng)你吹了這么長(zhǎng)時(shí)間。這個(gè)模式有用嗎?使用場(chǎng)景舉個(gè)例子。


lizi

其他的不說(shuō),就說(shuō)一個(gè)Android中的Application
......

最后我們看一下較為正式的定義。

單例模式:保證一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn) (記住,是壟斷,只能通過(guò)我的途徑拿貨)。

好了,今天就到這兒,下期見(jiàn)!

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

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

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