單例模式的七種寫法喇肋,你會幾種?(轉(zhuǎn)載)

原文作者:Cantellow
原文地址:原文鏈接
摘抄申明:我們不占有不侵權(quán)迹辐,我們只是好文的搬運(yùn)工蝶防!轉(zhuǎn)發(fā)請帶上原文申明。

第一種(懶漢明吩,線程不安全):

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  

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

第二種(懶漢间学,線程安全):

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

這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading印荔,但是低葫,遺憾的是,效率很低仍律,99%情況下不需要同步嘿悬。

第三種(餓漢):

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

這種方式基于classloder機(jī)制,在深度分析Java的ClassLoader機(jī)制(源碼級別)和Java類的加載水泉、鏈接和初始化兩個文章中有關(guān)于Classload而機(jī)制的線程安全問題的介紹善涨,避免了多線程的同步問題,不過草则,instance在類裝載時就實(shí)例化躯概,雖然導(dǎo)致類裝載的原因有很多種,在單例模式中大多數(shù)都是調(diào)用getInstance方法畔师, 但是也不能確定有其他的方式(或者其他的靜態(tài)方法)導(dǎo)致類裝載娶靡,這時候初始化instance顯然沒有達(dá)到lazy loading的效果。

第四種(餓漢看锉,變種):

public class Singleton {  
    private Singleton instance = null;  
    static {  
    instance = new Singleton();  
    }  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return this.instance;  
    }  
}

表面上看起來差別挺大姿锭,其實(shí)更第三種方式差不多,都是在類初始化即實(shí)例化instance伯铣。

第五種(靜態(tài)內(nèi)部類):

public class Singleton {  
    private static class SingletonHolder {  
      private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
      return SingletonHolder.INSTANCE;  
    }  
}

這種方式同樣利用了classloder的機(jī)制來保證初始化instance時只有一個線程呻此,它跟第三種和第四種方式不同的是(很細(xì)微的差別):第三種和第四種方式是只要Singleton類被裝載了,那么instance就會被實(shí)例化(沒有達(dá)到lazy loading效果)腔寡,而這種方式是Singleton類被裝載了焚鲜,instance不一定被初始化。因?yàn)镾ingletonHolder類沒有被主動使用放前,只有顯示通過調(diào)用getInstance方法時忿磅,才會顯示裝載SingletonHolder類,從而實(shí)例化instance凭语。想象一下葱她,如果實(shí)例化instance很消耗資源,我想讓他延遲加載似扔,另外一方面吨些,我不希望在Singleton類加載時就實(shí)例化搓谆,因?yàn)槲也荒艽_保Singleton類還可能在其他的地方被主動使用從而被加載,那么這個時候?qū)嵗痠nstance顯然是不合適的豪墅。這個時候泉手,這種方式相比第三和第四種方式就顯得很合理。

第六種(枚舉):

public enum SingletonEnum{
    INSTANCE;  
    private Singleton singleton ;
    // JVM保證這個方法絕對只調(diào)用一次
    SingletonEnum(){
        singleton = new Singleton();
    }
    public Singleton  getInstance() {  
          return singleton ;
    }  
}

調(diào)用:Singleton  singleton = SingletonEnum.INSTANCE.getInstance();

這種方式是Effective Java作者Josh Bloch 提倡的方式偶器,它不僅能避免多線程同步問題螃诅,而且還能防止反序列化重新創(chuàng)建新的對象,可謂是很堅(jiān)強(qiáng)的壁壘啊状囱,在深度分析Java的枚舉類型——枚舉的線程安全性及序列化問題中有詳細(xì)介紹枚舉的線程安全問題和序列化問題(枚舉類沒有構(gòu)造方法术裸,不會被反序列化),不過亭枷,個人認(rèn)為由于1.5中才加入enum特性袭艺,用這種方式寫不免讓人感覺生疏,在實(shí)際工作中叨粘,我也很少看見有人這么寫過猾编。

原文補(bǔ)充:這個枚舉沒看出來在哪創(chuàng)造的對象,所以有上網(wǎng)查了一下 java單例之enum實(shí)現(xiàn)方式

public class EnumSingleton{
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }
    
    private static enum Singleton{
        INSTANCE;
        
        private EnumSingleton singleton;
        //JVM會保證此方法絕對只調(diào)用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}

第七種(雙重校驗(yàn)鎖):

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  // 出于性能考慮升敲,第一次判斷null答倡,避免已實(shí)例化再次進(jìn)入同步代碼塊
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

這個是第二種方式的升級版,俗稱雙重檢查鎖定驴党,詳細(xì)介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html已失效瘪撇,重新?lián)Q了鏈接

在JDK1.5之后,雙重檢查鎖定才能夠正常達(dá)到單例效果港庄。

總結(jié)

有兩個問題需要注意:

1.如果單例由不同的類裝載器裝入倔既,那便有可能存在多個單例類的實(shí)例。假定不是遠(yuǎn)端存取鹏氧,例如一些servlet容器對每個servlet使用完全不同的類裝載器渤涌,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實(shí)例把还。

2.如果Singleton實(shí)現(xiàn)了java.io.Serializable接口实蓬,那么這個類的實(shí)例就可能被序列化和復(fù)原。不管怎樣吊履,如果你序列化一個單例類的對象安皱,接下來復(fù)原多個那個對象,那你就會有多個單例類的實(shí)例率翅。

對第一個問題修復(fù)的辦法是:

private static Class getClass(String classname)      
                                         throws ClassNotFoundException {     
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
         classLoader = Singleton.class.getClassLoader();     
      
      return (classLoader.loadClass(classname));     
   }     
}

對第二個問題修復(fù)的辦法是:

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
      
   protected Singleton() {     
        
   }     
   private Object readResolve() {     
            return INSTANCE;     
      }    
} 

對我來說练俐,我比較喜歡第三種和第五種方式袖迎,簡單易懂冕臭,而且在JVM層實(shí)現(xiàn)了線程安全(如果不是多個類加載器環(huán)境)腺晾,一般的情況下,我會使用第三種方式辜贵,只有在要明確實(shí)現(xiàn)lazy loading效果時才會使用第五種方式悯蝉,另外,如果涉及到反序列化創(chuàng)建對象時我會試著使用枚舉的方式來實(shí)現(xiàn)單例托慨,不過鼻由,我一直會保證我的程序是線程安全的,而且我永遠(yuǎn)不會使用第一種和第二種方式厚棵,如果有其他特殊的需求蕉世,我可能會使用第七種方式,畢竟婆硬,JDK1.5已經(jīng)沒有雙重檢查鎖定的問題了狠轻。


superheizai同學(xué)總結(jié)的很到位:

不過一般來說,第一種不算單例彬犯,第四種和第三種就是一種向楼,如果算的話,第五種也可以分開寫了谐区。所以說湖蜕,一般單例都是五種寫法。懶漢宋列,惡漢昭抒,雙重校驗(yàn)鎖,枚舉和靜態(tài)內(nèi)部類炼杖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末戈鲁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子嘹叫,更是在濱河造成了極大的恐慌婆殿,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罩扇,死亡現(xiàn)場離奇詭異婆芦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)喂饥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門消约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人员帮,你說我怎么就攤上這事或粮。” “怎么了捞高?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵氯材,是天一觀的道長渣锦。 經(jīng)常有香客問我,道長氢哮,這世上最難降的妖魔是什么袋毙? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮冗尤,結(jié)果婚禮上听盖,老公的妹妹穿的比我還像新娘。我一直安慰自己裂七,他們只是感情好皆看,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著背零,像睡著了一般悬蔽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捉兴,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天蝎困,我揣著相機(jī)與錄音,去河邊找鬼倍啥。 笑死禾乘,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虽缕。 我是一名探鬼主播始藕,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氮趋!你這毒婦竟也來了伍派?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤剩胁,失蹤者是張志新(化名)和其女友劉穎诉植,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昵观,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晾腔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啊犬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灼擂。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖觉至,靈堂內(nèi)的尸體忽然破棺而出剔应,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布峻贮,位于F島的核電站席怪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏月洛。R本人自食惡果不足惜何恶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一孽锥、第九天 我趴在偏房一處隱蔽的房頂上張望嚼黔。 院中可真熱鬧,春花似錦惜辑、人聲如沸唬涧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碎节。三九已至,卻和暖如春抵卫,著一層夾襖步出監(jiān)牢的瞬間狮荔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工介粘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留殖氏,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓姻采,卻偏偏與公主長得像雅采,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子慨亲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容