2.DCL分析

DCL, 即(Double Check Lock),中文:雙重檢查鎖定

1.問(wèn)題分析

我們先看看單例模式里面的懶漢式

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static Singleton getInstance(){
        if (singleton ==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

以上是無(wú)法保證他是線程的安全性,優(yōu)化如下:

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static synchronize Singleton getInstance(){
        if (singleton ==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

以上優(yōu)化就確保了線程的安全性,但是性能非常的低效,導(dǎo)致性能下降,那么我們用DCL雙重檢查來(lái)優(yōu)化

public class Singleton{
    private static Singleton singleton;
    
    private Singleton(){}
    
    public static  Singleton getInstance(){
        if (singleton ==null){
            synchronize(Singleton.class){
                if (singleton == null){
                singleton  = new Singleton();
                }
            }
        }
        return singleton;
    }
}

以上代碼看起來(lái)很完美吧,那我們來(lái)分析一下:

  1. 如果檢查第一個(gè)singleton不為null,則不需要執(zhí)行以下代碼,提高了性能
  2. 如果第一個(gè)singleton為neull,即使有多個(gè)線程同一個(gè)時(shí)間判斷,但是由于synchronize的存在,只會(huì)有一個(gè)線程能夠創(chuàng)建對(duì)象
  3. 當(dāng)?shù)谝粋€(gè)獲取鎖的線程創(chuàng)建完成后singleton對(duì)象后,其他的第二次判斷singleton一定會(huì)為null,則直接返回已經(jīng)創(chuàng)建好的singleton對(duì)象

是不是很完美的代碼,邏輯上是完全沒(méi)有問(wèn)題的. 但是事實(shí)上并不是,上面的邏輯在jvm指令排序是存在問(wèn)題的.jvm創(chuàng)建實(shí)例化對(duì)象的三個(gè)步驟

memory = allocate();   //1: 分配內(nèi)存空間
ctorInstance(memory);  //2: 初始化對(duì)象
instance = memory;     //3: 將內(nèi)存空間的地址賦值給對(duì)象的引用

但是由于重排序的原因,步驟2,3可能會(huì)發(fā)生重排序,其過(guò)程如下

memory = allocate();   //1: 分配內(nèi)存空間
instance = memory;     //3: 將內(nèi)存空間的地址賦值給對(duì)象的引用
ctorInstance(memory);  //2: 初始化對(duì)象

以上的排序就會(huì)導(dǎo)致第二個(gè)判斷會(huì)出錯(cuò) singleton !=null 但是這時(shí)該對(duì)象只是個(gè)內(nèi)存地址,并沒(méi)有初始化實(shí)例,所以return的是singleton的內(nèi)存地址,導(dǎo)致第二個(gè)線程獲取到的是singleton的內(nèi)存地址,引用起來(lái)就會(huì)拋錯(cuò)

此時(shí)就知道錯(cuò)誤在哪里了

singleton = new Singleton();

知道問(wèn)題根源,解決方法有兩個(gè):

  • 不允許初始化階段步驟發(fā)生重排序
  • 允許重排序,但是不允許其他線程看到這個(gè)排序(即單例的餓漢單例模式)

方案一:

基于以上代碼修改:將變量singleton聲明為volatile

public class Singleton {

    // 通過(guò)volatile關(guān)鍵字來(lái)確保安全
    private volatile static Singleton singleton;
    private Singleton(){}

    public static Singleton getInstance(){
        if(singleton == null){
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

當(dāng)singleton聲明為volatile后,new實(shí)例化的步驟就不會(huì)重新排序了

方案二:(使用單例模式的餓漢模式)

原理: 利用ClassLoder的機(jī)制,保證初始化instance時(shí)只有一個(gè)線程,jvm在類初始化階段會(huì)獲取一個(gè)鎖,這個(gè)鎖可以同步多個(gè)線程對(duì)一個(gè)類的初始化

public class Singleton {

    private static class SingletonHolder{
        public static Singleton singleton = new Singleton();
    }

    public static Singleton getInstance(){
        return SingletonHolder.singleton;
    }
}

原理圖:


image
?著作權(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ù)。

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