設計模式(一):“懶漢式”與“餓漢式”單例模式 - 新聞資訊 - 雲南小程序開發|雲南軟件開發|雲南網站建設-昆明融晨信息技術有限公司

159-8711-8523

雲南網建設/小程序開發/軟件開發

知識

不(bù)管是(shì)網站,軟件還是(shì)小程序,都要(yào / yāo)直接或間接能爲(wéi / wèi)您産生價值,我們在(zài)追求其視覺表現的(de)同時(shí),更側重于(yú)功能的(de)便捷,營銷的(de)便利,運營的(de)高效,讓網站成爲(wéi / wèi)營銷工具,讓軟件能切實提升企業内部管理水平和(hé / huò)效率。優秀的(de)程序爲(wéi / wèi)後期升級提供便捷的(de)支持!

您當前位置>首頁 » 新聞資訊 » 技術分享 >

設計模式(一):“懶漢式”與“餓漢式”單例模式

發表時(shí)間:2019-7-5

發布人(rén):融晨科技

浏覽次數:50

何爲(wéi / wèi)單例模式?單例模式即一個(gè)類隻有一個(gè)實例并且該類有提供一個(gè)全局訪問點。我們常常希望某個(gè)對象實例隻有一個(gè),不(bù)想要(yào / yāo)頻繁地(dì / de)創建和(hé / huò)銷毀對象,浪費系統資源,這(zhè)時(shí)候我們就(jiù)要(yào / yāo)使用單例模式來(lái)獲取類的(de)實例。那怎麽才能保證一個(gè)類是(shì)單例的(de)呢?

我們可以(yǐ)先讓一個(gè)類的(de)構造方法給私有化,這(zhè)樣外部就(jiù)不(bù)能通過new來(lái)創建類的(de)對象,然後使用靜态變量instance将實例保存起來(lái),當需要(yào / yāo)使用該類的(de)實例時(shí)隻要(yào / yāo)調用getInatance()方法就(jiù)可以(yǐ)得到(dào)該類的(de)實例了(le/liǎo)。代碼如下:

class SingleObject{
    private static SingleObject instance;

    private SingleObject(){
        
    }

    public static SingleObject getInstance(){
        if(instance == null){
            instance = new SingleObject();
            return instance;
        }
    }
}

但這(zhè)樣就(jiù)足夠了(le/liǎo)嗎?如果隻有單個(gè)線程運行程序的(de)情況下,确實隻會返回一個(gè)實例,但如果在(zài)多線程情況下,還是(shì)可能會産生多個(gè)實例,爲(wéi / wèi)什麽?

舉個(gè)例子(zǐ),如果線程a在(zài)執行完getInstance方法裏的(de)if(instance == null)這(zhè)句代碼後就(jiù)因爲(wéi / wèi)時(shí)間片用完變成阻塞狀态,然後線程b執行getInstance方法時(shí),此時(shí)instance實例還沒有被new出(chū)來(lái),所以(yǐ)線程b就(jiù)執行if代碼塊裏的(de)代碼創建了(le/liǎo)一個(gè)SingleObject對象,并賦值給instance,然後結束。線程a被喚醒後,繼續執行之(zhī)前的(de)代碼,也(yě)就(jiù)是(shì)if代碼塊裏的(de)代碼,這(zhè)時(shí)就(jiù)會産生SingleObject類的(de)多個(gè)實例

爲(wéi / wèi)了(le/liǎo)解決多線程情況下可能會産生多個(gè)實例的(de)問題,我們可以(yǐ)使用synchronized關鍵字來(lái)給産生instance實例的(de)代碼塊“加鎖”解決這(zhè)個(gè)問題:

class SingleObject{
     private static SingleObject instance;
     public static SingleObject getInstance(){
        synchronized (instance){
            if(instance == null){
                instance = new SingleObject();
                return instance;
            }
        }
        return instance;
    }
}

通過synchronized關鍵字給創建實例的(de)代碼塊“加鎖”,同一時(shí)刻下,隻有一個(gè)線程能夠執行這(zhè)個(gè)代碼塊裏的(de)代碼,先執行這(zhè)個(gè)代碼塊的(de)線程發現沒有instance實例後會創建instance實例,後面執行的(de)線程會因爲(wéi / wèi)該實例已經存在(zài)而(ér)不(bù)會再去創建instance實例。

不(bù)過這(zhè)并不(bù)是(shì)完美的(de)解決方案,隻要(yào / yāo)是(shì)鎖,必然有性能損耗問題。而(ér)且對于(yú)上(shàng)面的(de)代碼,其實我們隻需要(yào / yāo)在(zài)線程第一次訪問時(shí)加鎖即可,之(zhī)後并不(bù)需要(yào / yāo)鎖,鎖給我們帶來(lái)了(le/liǎo)系統資源浪費。所以(yǐ)我們可以(yǐ)試着優化一下,如下面代碼:

public class SingleObject {

    private static SingleObject instance;

    private SingleObject(){

    }

    public static SingleObject getInstance(){
        if (instance == null){
            synchronized (instance){
                if(instance == null){
                    instance = new SingleObject();
                    return instance;
                }
            }
        }
        return instance;
    }
}

我們可以(yǐ)在(zài)同步代碼塊外再加一個(gè)判斷對象實例是(shì)否存的(de)if語句,這(zhè)樣大(dà)部分線程執行完第一個(gè)if判斷後就(jiù)不(bù)用進入同步代碼塊中,而(ér)是(shì)直接獲得instance對象,避免了(le/liǎo)加鎖解鎖的(de)資源消耗。

經過我們使用雙重判斷的(de)優化,看似已經完美解決了(le/liǎo)多線程情況下出(chū)現多個(gè)實例的(de)問題,其實還留有一個(gè)隐患,這(zhè)個(gè)隐患在(zài)第二個(gè)if判斷的(de)代碼塊裏的(de)instance = new SingleObject()這(zhè)一句代碼上(shàng),這(zhè)句代碼在(zài)JVM中不(bù)是(shì)一個(gè)原子(zǐ)操作,而(ér)是(shì)先有一條字節碼指令來(lái)調用SingleObject對象的(de)構造方法,然後下一條字節碼指令将SingleObject對象的(de)地(dì / de)址賦值給instance引用,但由于(yú)JVM的(de)指令重排序優化,這(zhè)兩條指令的(de)執行順序會發生颠倒,即先将SingleObject對象的(de)地(dì / de)址賦值給instance引用,再調用SingleObject對象的(de)構造方法,這(zhè)樣會出(chū)現的(de)情況就(jiù)是(shì),第一個(gè)線程在(zài)執行将SingleObject對象的(de)地(dì / de)址賦值給instance引用賦值這(zhè)條指令後,由于(yú)cpu時(shí)間片用完,而(ér)沒有調用SingleObject對象的(de)構造方法後就(jiù)阻塞,接着第二個(gè)線程在(zài)執行第一個(gè)if判斷時(shí),此時(shí)的(de)instance已經有值,直接return instance,但是(shì)此時(shí)的(de)instance引用指向的(de)對象并沒有調用構造方法,所以(yǐ)是(shì)個(gè)空對象,這(zhè)樣就(jiù)出(chū)現了(le/liǎo)問題。

那該如何解決這(zhè)個(gè)問題呢,我們可以(yǐ)使用volatile關鍵字來(lái)修飾instance變量,代碼如下:

public class SingleObject {

    private static volatile SingleObject instance;

    private SingleObject(){

    }

    public static SingleObject getInstance(){
        if (instance == null){
            synchronized (instance){
                if(instance == null){
                    instance = new SingleObject();
                    return instance;
                }
            }
        }
        return instance;
    }
}

volatile關鍵字可以(yǐ)禁用被修飾變量的(de)讀寫操作指令的(de)重排序,所以(yǐ)instance = new SingleObject()這(zhè)一句代碼将會按照先調用構造方法、再賦值引用的(de)順序執行,這(zhè)樣的(de)話,當第一個(gè)線程進入阻塞狀态時(shí),第二個(gè)線程在(zài)第一個(gè)if判斷時(shí)會因爲(wéi / wèi)instance的(de)值爲(wéi / wèi)空而(ér)進入第一個(gè)if代碼塊中,但if代碼塊中的(de)代碼被synchronized給鎖住,而(ér)此時(shí)鎖又被第一個(gè)線程擁有,所以(yǐ)第二個(gè)線程會進入鎖對象的(de)等待隊列中等待。而(ér)當第一個(gè)線程再次獲得時(shí)間片時(shí),它會繼續實例化SingleObject對象并賦值給instance引用,然後結束并釋放鎖。而(ér)第二個(gè)線程被喚醒後拿到(dào)鎖執行第二個(gè)if判斷,因爲(wéi / wèi)instance已經有值,所以(yǐ)直接結束并釋放鎖。這(zhè)樣就(jiù)完美地(dì / de)解決了(le/liǎo)多線程模式下可能會産生多個(gè)實例的(de)問題。

上(shàng)面創建單例對象的(de)方式都是(shì)在(zài) getInstance() 方法中創建實例,也(yě)就(jiù)是(shì)說(shuō)在(zài)要(yào / yāo)調用的(de)時(shí)候才創建實例,這(zhè)種方式被稱爲(wéi / wèi) “ 懶漢式 ” 我們也(yě)可以(yǐ)使用“餓漢式”單例模式 ,在(zài)類加載時(shí)就(jiù)創建好了(le/liǎo)實例,代碼如下:

class SingleObject{
    private static final SingleObject instance = new SingleObject();

    private SingleObject(){

    }

    public static SingleObject getInstance(){
        return instance;
    }
}

“餓漢式”的(de)單例模式就(jiù)是(shì)在(zài)單例類裏面去定義和(hé / huò)實例化一個(gè)靜态的(de)單例類對象,這(zhè)樣在(zài)單例類被加載時(shí)靜态變量的(de)單例對象就(jiù)會被創建出(chū)來(lái),不(bù)用去擔心線程安全等問題。但是(shì),這(zhè)樣做的(de)壞處是(shì)不(bù)管你這(zhè)個(gè)項目用不(bù)用到(dào)這(zhè)個(gè)單例類對象,該對象都會被創建出(chū)來(lái),可能會造成資源浪費。

相關案例查看更多