單例模式探究

單例模式的使用場(chǎng)景:

  • 產(chǎn)生某對(duì)象會(huì)消耗過多的資源定嗓,為避免頻繁地創(chuàng)建與銷毀對(duì)象對(duì)資源的浪費(fèi)枯怖。如:
    對(duì)數(shù)據(jù)庫(kù)的操作注整、訪問 IO、線程池度硝、網(wǎng)絡(luò)請(qǐng)求等肿轨。

  • 某種類型的對(duì)象應(yīng)該有且只有一個(gè)。如果制造出多個(gè)這樣的實(shí)例蕊程,可能導(dǎo)致:程序行為異常椒袍、資源使用過量、結(jié)果不一致等問題存捺。

單例模式的幾種寫法

  1. 餓漢槐沼,線程安全
public class Singleton {
    public static Singleton instance = new Singleton();
    private Singleton (){}
}
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
        return instance;
    }
}

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

這三種方式?jīng)]什么差別曙蒸,都依賴 JVM 在類裝載時(shí)就完成唯一對(duì)象的實(shí)例化,基于類加載的機(jī)制岗钩,它們天生就是線程安全的纽窟,在急切初始化的方案下都是可行的。

  1. 懶漢兼吓,線程安全
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}  

這種寫法能夠在多線程中很好的工作臂港,而且看起來它也具備很好的lazy loading,但是视搏,效率很低审孽,只有new對(duì)象的是時(shí)候需要同步,對(duì)象創(chuàng)建好了后再取對(duì)象的時(shí)候是不需要同步的浑娜。

所以我們可以將它改進(jìn)為另一種形式佑力,被稱為“雙重檢查鎖定的方式”

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

這種方法的“雙重檢查”體現(xiàn)在進(jìn)行了兩次 if (singleton == null) 的檢查,這樣既同步代碼塊保證了線程安全筋遭,同時(shí)實(shí)例化的代碼也只會(huì)執(zhí)行一次打颤,實(shí)例化后同步操作不會(huì)再被執(zhí)行,從而效率提升很多

雙重檢查鎖定存在的問題是漓滔,在操作指令重排序的情況下编饺,可能會(huì)導(dǎo)致對(duì)象不唯一 ,所以要在定義單例時(shí)加上 volatile 關(guān)鍵字修飾响驴,保證執(zhí)行的順序透且,就可以使單例起效。

  1. 靜態(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時(shí)只有一個(gè)線程豁鲤,這種方式的 Singleton 類被裝載時(shí)秽誊,只要 SingletonHolder 類還沒有被主動(dòng)使用,instance 就不會(huì)被初始化。只有在顯式調(diào)用 getInstance() 方法時(shí),才會(huì)裝載 SingletonHolder 類矾端,實(shí)例化對(duì)象洞就,實(shí)現(xiàn)了延遲加載。
“靜態(tài)內(nèi)部類”方式與“雙重檢查鎖定”方式相比的優(yōu)勢(shì)在于“雙重檢查鎖定” 方式在 JDK 版本低于 1.5 時(shí)多線程環(huán)境下可能會(huì)失效肾胯,而“靜態(tài)內(nèi)部類”則不受JDK版本的限制竖席。

  1. 枚舉
public enum Singleton {  
    INSTANCE;  
    public void dosomething() {  
    }  
}  

這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題敬肚,而且還能防止反序列化重新創(chuàng)建新的對(duì)象毕荐。保證了在任何情況(包括反序列化、反射艳馒、克略餮恰)下都是一個(gè)單例员寇,不過由于枚舉是 JDK 1.5 才加入的特性,所以同“雙重檢查鎖定” 方式一樣第美,它對(duì) JDK 的版本也有要求

  1. 登記式單例——使用 Map 容器來管理單例模式
public class SingletonManager {
    private static Map<String, Object> objMap = new HashMap()<String, Object>;
    public static void registService(String key, Object instance) {
        if(!objMap.containsKey(key))
            objMap.put(key, instance);
   }
   public static Object getService(String key) {
       return objMap.getKey();
   }
}

在程序的初始蝶锋,我們將一組單例類型注入到一個(gè)統(tǒng)一的管理類中來維護(hù),即將這些實(shí)例存放在一個(gè) Map 登記薄中什往,在使用時(shí)則根據(jù) key 來獲取對(duì)象對(duì)應(yīng)類型的單例對(duì)象扳缕。對(duì)于已經(jīng)登記過的實(shí)例,從 Map 直接返回實(shí)例别威;對(duì)于沒有登記的躯舔,則先登記再返回。從而在對(duì)用戶隱藏具體實(shí)現(xiàn)省古、降低代碼耦合度的同時(shí)粥庄,也降低了用戶的使用成本

需要注意的對(duì)單例模式的破壞

  • 如果Singleton實(shí)現(xiàn)了java.io.Serializable接口,那么這個(gè)類的實(shí)例就可能被序列化和復(fù)原豺妓。要避免單例對(duì)象在反序列化時(shí)重新生成對(duì)象惜互,則在 implements Serializable 的同時(shí)應(yīng)該實(shí)現(xiàn) readResolve() 方法,并在其中保證反序列化的時(shí)候獲得原來的對(duì)象:
public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
      
   protected Singleton() {     
        
   }     
   private Object readResolve() {     
          return INSTANCE;     
   }    
}   
  • 使用反射調(diào)利用私有構(gòu)造器也可以破壞單例科侈,要防止此情況發(fā)生载佳,可以在私有的構(gòu)造器中加一個(gè)判斷,需要?jiǎng)?chuàng)建的對(duì)象不存在就創(chuàng)建臀栈;存在則說明是第二次調(diào)用蔫慧,拋出 RuntimeException 提示。修改私有構(gòu)造函數(shù)代碼如下:
public class Singleton {     
   ...
   private Singleton() {     
        if(instance != null)
            throw new RuntimeException("不能創(chuàng)建多個(gè)Singleton對(duì)象");
   }     
    ... 
}   
  • 通過克隆來創(chuàng)建一個(gè)新對(duì)象权薯,單例模式就失效了姑躲。單例模式的類是不可以實(shí)現(xiàn) Cloneable 接口的,這與 Singleton 模式的初衷相違背盟蚣。那要如何阻止使用 clone() 方法創(chuàng)建單例實(shí)例的另一個(gè)實(shí)例黍析?可以 override 它的 clone() 方法,使其拋出異常屎开。(也許你想問既然知道了某個(gè)類是單例且單例不應(yīng)該實(shí)現(xiàn) Cloneable 接口阐枣,那不實(shí)現(xiàn)該接口不就可以了嗎?事實(shí)上盡管很少見奄抽,但有時(shí)候單例類可以繼承自其它類蔼两,如果其父類實(shí)現(xiàn)了 clone() 方法的話,就必須在我們的單例類中復(fù)寫 clone() 方法來阻止對(duì)單例的破壞逞度。)
@Override
public class Singleton implements  Cloneable {
    ...
    proteced Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    ...
}
  • 不同的類加載器可以加載同一個(gè)類额划。所以當(dāng)一個(gè)工程下面存在不止一個(gè)類加載器時(shí),整個(gè)程序中同一個(gè)類就可能被加載多次档泽,如果這是個(gè)單例類就會(huì)產(chǎn)生多個(gè)單例并存失效的現(xiàn)象俊戳。因此當(dāng)程序有多個(gè)類加載器又需要實(shí)現(xiàn)單例模式揖赴,就須自行指定類加載器,并要指定同一個(gè)類加載器
private static Class getClass(String classname) throws ClassNotFoundException {     
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
           classLoader = Singleton.class.getClassLoader();     
      return (classLoader.loadClass(classname));     
   }     
}  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末抑胎,一起剝皮案震驚了整個(gè)濱河市燥滑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌圆恤,老刑警劉巖突倍,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異盆昙,居然都是意外死亡羽历,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門淡喜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秕磷,“玉大人,你說我怎么就攤上這事炼团∨煜” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵瘟芝,是天一觀的道長(zhǎng)易桃。 經(jīng)常有香客問我,道長(zhǎng)锌俱,這世上最難降的妖魔是什么晤郑? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮贸宏,結(jié)果婚禮上造寝,老公的妹妹穿的比我還像新娘。我一直安慰自己吭练,他們只是感情好诫龙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鲫咽,像睡著了一般签赃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上分尸,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天姊舵,我揣著相機(jī)與錄音,去河邊找鬼寓落。 笑死,一個(gè)胖子當(dāng)著我的面吹牛荞下,可吹牛的內(nèi)容都是我干的伶选。 我是一名探鬼主播史飞,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼仰税!你這毒婦竟也來了构资?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤陨簇,失蹤者是張志新(化名)和其女友劉穎吐绵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體河绽,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡己单,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耙饰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纹笼。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖苟跪,靈堂內(nèi)的尸體忽然破棺而出廷痘,到底是詐尸還是另有隱情,我是刑警寧澤件已,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布笋额,位于F島的核電站,受9級(jí)特大地震影響篷扩,放射性物質(zhì)發(fā)生泄漏兄猩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一瞻惋、第九天 我趴在偏房一處隱蔽的房頂上張望厦滤。 院中可真熱鬧,春花似錦歼狼、人聲如沸掏导。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趟咆。三九已至,卻和暖如春梅屉,著一層夾襖步出監(jiān)牢的瞬間值纱,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工坯汤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虐唠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓惰聂,卻偏偏與公主長(zhǎng)得像疆偿,于是被迫代替她去往敵國(guó)和親咱筛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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