成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

單例終極分析(一)

Jenny_Tong / 2135人閱讀

摘要:好,看看大家喜聞樂見的并發(fā)場景下,這種簡易的寫法會出現(xiàn)什么問題兩個線程和同時訪問,它們都覺得判斷成立,分別執(zhí)行了步驟,成功創(chuàng)建出對象但是,我們通篇都在聊單例啊,和的玩法無疑很不單例問題分析出來了,而解決上并不復(fù)雜讓線程同步就好。

單例的用處

如果你看過設(shè)計模式,肯定會知道單例模式,實際上這是我能默寫出代碼的第一個設(shè)計模式,雖然很長一段時間我并不清楚單例具體是做什么用的。
這里簡單提一下單例的用處。作為java程序員,你應(yīng)該知道spring框架,而其中最核心的IOC,在默認情況下注入的Bean就是單例的。有什么好處?那些Service、Dao等只創(chuàng)建一次,不必每次都通過new方式創(chuàng)建,也就不用每次都開辟空間、垃圾回收等等,會省不少資源。

version 1: 餓漢式

那么如何寫一個單例呢?我想很多朋友都能搞定:

public class Singleton {

    private static final Singleton singletonInstance = new Singleton();    // A - 急不可待的成員變量賦值,static和final修飾
    private Singleton (){}    // B - 私有化的構(gòu)造器,避免隨意new

    public static Singleton getInstance(){    // C - 暴露給外部的獲取方法
        return singletonInstance;
    }
}

Ok,擁有A、B、C三大特點(注釋部分),就構(gòu)成了著名的餓漢式單例。好處在于簡單粗暴,易于理解(只要你真正通曉finalstatic的作用)。
但有豪放派,就有婉約派。后來大家都覺得,我還沒有使用這個類,你就直接把對象構(gòu)建出來扔java堆里了,是不是有點不那么含蓄?

于是大家快速迭代出懶漢式單例。

version 2: 懶漢式
class Singleton {

    private static Singleton singletonInstance;     // A - 溫婉到只有變量聲明
    private Singleton (){}      // B 

    public static Singleton getInstance(){      // C 
        if(singletonInstance==null){
            singletonInstance = new Singleton();    // D - 成員變量的創(chuàng)建賦值延后至此
        }
        return singletonInstance;
    }
}

變化發(fā)生于A、D兩步,總得來說,就是把成員變量singletonInstance的創(chuàng)建和賦值延后了。基本的要求達到了,在沒調(diào)用getInstance()方法之前,對象無創(chuàng)建,不再麻煩java堆大大。一切看起來都很美好,但僅限于單線程情況下。
好,看看大家喜聞樂見的并發(fā)場景下,這種簡易的寫法會出現(xiàn)什么問題——兩個線程T-1T-2同時訪問getInstance(),它們都覺得singletonInstance==null判斷成立,分別執(zhí)行了步驟D,成功創(chuàng)建出singletonInstance對象!但是,我們通篇都在聊單例啊,T-1T-2的玩法無疑很不單例!
問題分析出來了,而解決上并不復(fù)雜——讓線程同步就好。

version 2.1: 簡易解決并發(fā)的懶漢式
class Singleton {

    private static Singleton singletonInstance;     // A
    private Singleton (){}      // B

    public static synchronized Singleton getInstance(){      // C - 用synchronized關(guān)鍵字修飾
        if(singletonInstance==null){
            singletonInstance = new Singleton();    // D
        }
        return singletonInstance;
    }
}

唯一的變化在于步驟C,加入了synchronized關(guān)鍵字,讓線程同步執(zhí)行此方法。現(xiàn)在問題解決了,不管線程T-1還是T-2,在getInstance()面前都要小朋友們排排坐——一個個執(zhí)行,這樣即使是線程T-100甚至T-500過來也要排隊執(zhí)行,哈哈哈哈哈哈……嗚嗚嗚……
既是解決方案,也是問題所在,這種方式效率太差了!

我們知道,synchronized有另一種使用方式就是鎖代碼塊,可以減少鎖粒度。

class Singleton {

    private static Singleton singletonInstance;     // A
    private Singleton (){}      // B

    public static Singleton getInstance(){      
        synchronized (Singleton.class){    // C - 改成synchronized鎖代碼塊
            if(singletonInstance==null){
                singletonInstance = new Singleton();
            }
        }
        return singletonInstance;
    }
}

但在這個例子中,該方式看上去似乎沒什么提升(該方法主要邏輯只有singletonInstance = new Singleton()一行)。好在有聰明人,研究出了Double-check。

version 2.2: Double-check (有問題版)
class Singleton {

    private static Singleton singletonInstance;     // A
    private Singleton (){}      // B

    public static Singleton getInstance(){      
        if(singletonInstance==null){    // C1 - synchronized之前,第一次判斷
            synchronized (Singleton.class){    
                if(singletonInstance==null){    // C2 - synchronized之后,第二次判斷
                    singletonInstance = new Singleton();
                }
            }
        }
        return singletonInstance;
    }
}

我一直覺得這種方式很巧妙。C1的判斷用于非并發(fā)環(huán)境,阻攔對象創(chuàng)建后的大部分訪問;C2的判斷,解決首次創(chuàng)建對象時的并發(fā)問題。
很長一段時間,我覺得這就是最終方案了,世界再次變得美好,沒想到還是圖樣圖森破(too young, too simple!)。其實不止是單例,jdk1.5之前很多問題都被一個關(guān)鍵字耽擱了——volatile,而它相關(guān)的問題深深隱藏在Java內(nèi)存模型層面,且聽我緩緩道來……

version 2.3: volatile解決有序性

算了,照顧下沒耐性的開發(fā)兄弟,先給出修改方案:

class Singleton {

    private static volatile Singleton singletonInstance;     // A - 用volatile修飾
    private Singleton (){}      // B

    public static Singleton getInstance(){      
        if(singletonInstance==null){    // C1
            synchronized (Singleton.class){    
                if(singletonInstance==null){    // C2
                    singletonInstance = new Singleton();
                }
            }
        }
        return singletonInstance;
    }
}

可以看到,唯一的變化在于A位置加入了volatile關(guān)鍵字,用于解決有序性問題。volatile涉及的原子性可見性這里不作討論)

有序性

什么是有序性?舉個“栗子”:

int x=2;//語句1
int y=0;//語句2
boolean flag=true;//語句3
x=4;//語句4
y=-1;//語句5

對于上面的代碼來說,書寫語句按順序1至5,但執(zhí)行上很可能不是這樣。有可能是1-4-3-2-5,或者1-3-2-5-4,其實只要保證1在4前并且2在5前,剩下的順序可以隨意變化。這要感謝內(nèi)存模型同志,它天然允許編譯器和處理器對指令進行重排序。動機是好的——可以默默的幫你做些優(yōu)化,但在并發(fā)場景下,就有好心辦壞事的嫌疑。

看下另一個例子:

Context context = null;
boolean inited = false;

   //線程-1:
public void methodA(){
    context=loadContext();    //語句1
    inited=true;    //語句2
}

    //線程-2:
public void methodB(){
    while(!inited){
        sleep(1)    //語句3
    }
    doSomethingwithconfig(context);    //語句4
}

并發(fā)場景下,很可能出現(xiàn)如下情況:

線程-2語句3位置無憂無慮的休眠

語句2語句1發(fā)生指令重排,線程-1進入methodA()時先執(zhí)行語句2

恰逢線程-2覺醒,執(zhí)行語句4,此時context還是null(語句1context初始化還沒執(zhí)行),災(zāi)難產(chǎn)生

volatile,是個“擋板”,能保證執(zhí)行順序。為什么稱之為“擋板”?還以之前的“栗子”說明:

int x=2;//語句1
int y=0;//語句2
volatile boolean flag=true;    //語句3 - 用volatile修飾
x=4;//語句4
y=-1;//語句5

語句3boolean變量 用volatile修飾后,重排只能分別發(fā)生在1、2之間或語句4、5之間。即語句1、2不能跨過語句3,語句4、5也不能跨過語句3。

我們還需知道,對于java的某些操作,比如++,雖然看上去是一行代碼,但實質(zhì)上這個操作本身并不是原子的。以i++為例,該操作實際包含i的當前值獲取,i+1計算,以及i=的賦值操作三兄弟。

同樣的,singletonInstance = new Singleton()也非原子指令,包含:

對象內(nèi)存分配

初始化LazySingleton對象屬性

將singleton引用指向內(nèi)存空間

如果不用volatile修飾,萬惡的指令重排可能發(fā)生在步驟2步驟3之間,產(chǎn)生如下狀況(此處有盜圖嫌疑,罪過):

以上圖的情況,線程B獲取到了尚未初始化完全的LazySingleton對象,使得在后續(xù)的使用中出現(xiàn)異常! 用volatile修飾singleton變量后,指令重排技能被禁用,singletonInstance = new Singleton()只能按步驟1、2、3順序執(zhí)行,問題就此解決。

值得一提的是,其實存在更好的volatile修飾版本。

version 2.4:推薦的volatile + Double-check 版
class Singleton {

    private static volatile Singleton singletonInstance;     // A 
    private Singleton (){}      // B

    public static Singleton getInstance(){
        tempInstance = singletonInstance;    // C - 開啟了臨時變量
        if(tempInstance==null){    
            synchronized (Singleton.class){    
                if(tempInstance==null){
                    singletonInstance = tempInstance = new Singleton();
                }
            }
        }
        return tempInstance ;
    }
}

這種寫法差別在于在代碼C位置,聲明了變量tempInstance臨時變量,之后的邏輯都使用tempInstance代替singletonInstance。為什么要這樣做?wiki上準原文是這么說的:

Note the local variable "tempInstance ", which seems unnecessary. The effect of this is that in cases where singletonInstance is already initialized (i.e., most of the time), the volatile field is only accessed once (due to "return tempInstance ;" instead of "return singletonInstance;"), which can improve the method"s overall performance by as much as 25 percent.

翻譯一下就是:
singletonInstance對象大部分時候是已完成初始化的,用tempInstance臨時變量之后能減少volatile屬性(singletonInstance)的訪問,這么做大概能提升25%的性能!

后續(xù)

哇,一不小心寫了這么多,而且還沒結(jié)束,留待下一篇吧。(主要是volatile部分比較羅嗦了,這個關(guān)鍵字各位需好好看下,借以窺探內(nèi)存模型,原子性和可見性沒做分析都已經(jīng)占了這么大的篇幅)
下一篇文章會包含靜態(tài)內(nèi)部類實現(xiàn)單例、final+泛型實現(xiàn)單例、java9 VarHandler單例等,敬請期待?。〞腥似诖龁?::>_<:: )

參考資料

https://en.wikipedia.org/wiki...

https://www.cs.umd.edu/~pugh/...

https://www.jianshu.com/p/cf5...

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77849.html

相關(guān)文章

  • 單例模式的終極實現(xiàn)方案

    摘要:如此便可使得這一實現(xiàn)方式能夠同時具備線程安全延遲加載以及節(jié)省大量同步判斷資源等優(yōu)勢,可以說是單例模式的最佳實現(xiàn)了 單例模式(Singleton)是一種使用率非常高的設(shè)計模式,其主要目的在于保證某一類在運行期間僅被創(chuàng)建一個實例,并為該實例提供了一個全局訪問方法,通常命名為getInstance()方法。單例模式的本質(zhì)簡言之即是: 控制實例數(shù)目 以Java為例,單例模式通??煞譃轲I漢式和懶...

    Freelander 評論0 收藏0
  • vue-router 3.0版本中 router.push 不能刷新頁面的問題

    摘要:分析原因?qū)嵗蟮牟荒芩⑿马撁妫瑧?yīng)該是因為它與全局的中的的不是同一個,而之前的版本中能直接這樣使用,應(yīng)該是使用了單例。 在 github 的 vue-router 中找到同樣的一個問題:3.0.1版本通過router實例無法跳轉(zhuǎn) 昨天發(fā)現(xiàn)有些路由不能正常跳轉(zhuǎn),找了一下發(fā)現(xiàn)都是那些實例化后使用 router.push 而不是直接使用 this.$router.push 的地方。出現(xiàn)的情況是...

    xingqiba 評論0 收藏0
  • 再遇設(shè)計模式之JavaScript篇

    摘要:在面向?qū)ο蟮恼Z言中,比如,等,單例模式通常是定義類時將構(gòu)造函數(shù)設(shè)為,保證對象不能在外部被出來,同時給類定義一個靜態(tài)的方法,用來獲取或者創(chuàng)建這個唯一的實例。 萬事開頭難,作為正經(jīng)歷菜鳥賽季的前端player,已經(jīng)忘記第一次告訴自己要寫一些東西出來是多久以的事情了。。。如果,你也和我一樣,那就像我一樣,從現(xiàn)在開始,從看到這篇文章開始,打開電腦,敲下你的第一篇文章(或者任何形式的文字)吧。 ...

    Clect 評論0 收藏0
  • JS設(shè)計模式學(xué)習(xí)_基礎(chǔ)篇

    摘要:工廠模式單例模式結(jié)構(gòu)型設(shè)計模式關(guān)注于如何將類或?qū)ο蠼M合成更大更復(fù)雜的結(jié)構(gòu),以簡化設(shè)計。 一、寫在前面 設(shè)計模式的定義:在面向?qū)ο筌浖O(shè)計過程中針對特定問題的簡潔而優(yōu)雅的解決方案 當然我們可以用一個通俗的說法:設(shè)計模式是解決某個特定場景下對某種問題的解決方案。因此,當我們遇到合適的場景時,我們可能會條件反射一樣自然而然想到符合這種場景的設(shè)計模式。 比如,當系統(tǒng)中某個接口的結(jié)構(gòu)已經(jīng)無法滿足...

    venmos 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<