Java 單例實(shí)現(xiàn)解析

什么時(shí)候使用Singleton

Singleton指僅僅被實(shí)例化一次的類吮炕。Singleton通常用來代表那些本質(zhì)上唯一的系統(tǒng)組件歧蒋,比如文件系統(tǒng),窗口管理器偿曙,日歷等氮凝。Singleton的類會(huì)使客戶端測(cè)試變得異常困難,因?yàn)闊o法給Singleton替換模擬實(shí)現(xiàn)望忆,除非Singleton實(shí)現(xiàn)一個(gè)充當(dāng)其類型的接口罩阵。


Java類的實(shí)例化

按照是否調(diào)用類的構(gòu)造器,可以簡單的將類實(shí)例化的方法分為兩大類:通過構(gòu)造器實(shí)例化和不通過構(gòu)造器實(shí)例化启摄。下面以實(shí)例化 Windows10FileSystem 類為例進(jìn)行詳細(xì)說明稿壁。

Windows10FileSystem.java

public class Windows10FileSystem {
    private String name;
    private String description;
    // use default constructor
    // getter/setter method
}

這個(gè)文件系統(tǒng)十分的“簡陋”,只包括文件系統(tǒng)名稱和文件系統(tǒng)描述歉备。

通過構(gòu)造器實(shí)例化類

  • 通過 new 關(guān)鍵字傅是。

    Windows10FileSystem fileSystem = new Windows10FileSystem();
    
  • 通過 ClassnewInstance() 方法。

    Windows10FileSystem fileSystem = (Windows10FileSystem) Class.forName("Windows10FileSystem").newInstance();
    

    注意:forName的參數(shù)必須是類的全限定名蕾羊,這里為了簡單使用類名喧笔。

  • 通過 ConstructornewInstance() 方法。

    Constructor<Windows10FileSystem> constructor = Windows10FileSystem.class.getConstructor();
    Windows10FileSystem fileSystem = constructor.newInstance();
    

    上述方法只適用于構(gòu)造器可被訪問的場(chǎng)景肚豺,如果構(gòu)造器為private溃斋,可以使用下面的方法訪問構(gòu)造器界拦。

    如果 Windows10FileSystem的構(gòu)造器是私有(private)的吸申,借助 AccessibleObject.setAccessible(true) 可以調(diào)用私有的構(gòu)造器:

    Constructor<Windows10FileSystem> constructor = Windows10FileSystem.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    Windows10FileSystem fileSystem = constructor.newInstance();
    

不使用構(gòu)造器實(shí)例化類

  • 通過 clone() 函數(shù)。

    首先Windows10FileSystem 類需要實(shí)現(xiàn)clone()方法:

    @Override
    public Windows10FileSystem clone() {
        Windows10FileSystem copyFileSystem = new Windows10FileSystem();
        copyFileSystem.setDescription(this.description);
        copyFileSystem.setName(this.name);
        return copyFileSystem;
    }
    
    Windows10FileSystem fileSystem = new Windows10FileSystem();
    fileSystem.setName("clone");
    fileSystem.setDescription("graphical operating system");
    
    Windows10FileSystem cloneFileSystem = fileSystem.clone();
    
  • 通過反序列化享甸。

    假設(shè)保存對(duì)象的文件為:fileSystem.obj

    ObjectInputStream in = new ObjectInputStream(new FileInputStream("fileSystem.obj"));
    Windows10FileSystem fileSystem = (Windows10FileSystem) in.readObject();
    

Singleton的實(shí)現(xiàn)

實(shí)現(xiàn)Singleton的思路

上一節(jié)我們已經(jīng)了解Java中實(shí)例化一個(gè)類的多種方法截碴,而Singleton的目標(biāo)就是要確保類僅僅只被實(shí)例化一次,為此我們需要控制類實(shí)例化的入口蛉威,或者控制入口方法的調(diào)用次數(shù)或者控制方法每次調(diào)用返回同一個(gè)對(duì)象日丹,確保一個(gè)類只被實(shí)例化一次。

  • 構(gòu)造器私有化蚯嫌,降低類構(gòu)造器的可見范圍哲虾。

    private Windows10FileSystem() {}
    
  • 構(gòu)造器私有化雖然可以降低訪問范圍丙躏,但享有特權(quán)的客戶端可以借助 AccessibleObject.setAccessible(true) 方法,通過反射機(jī)制調(diào)用私有的構(gòu)造器束凑,為了抵御這種攻擊晒旅,需要修改私有構(gòu)造器,在構(gòu)造器第二次調(diào)用時(shí)拋出異常汪诉,阻止類被多次實(shí)例化废恋。

    private static AtomicBoolean FIRST_INSTANTIATION = new AtomicBoolean(true);
    
    private Windows10FileSystem() {
        if (FIRST_INSTANTIATION.get()) {
            FIRST_INSTANTIATION.compareAndSet(true,false);
        } else {
            throw  new UnsupportedOperationException();
        }
    }
    

    注意:這里沒有考慮并發(fā)調(diào)用構(gòu)造器的問題,在Java中可以使用鎖或synchronized簡單的進(jìn)行方法同步

  • 為了讓Singleton支持序列化扒寄,只實(shí)現(xiàn)Serializable 接口是不夠的鱼鼓,為了維護(hù)并保證Singleton,所有實(shí)例域都必須是瞬時(shí)(transient)的该编,并提供一個(gè)readResolve() 方法迄本,該方法每次都返回同一個(gè)實(shí)例。

    private static Windows10FileSystem INSTANCE = new Windows10FileSystem();
    
    private Object readResolve() {
        return INSTANCE;
    }
    

    該方法防止攻擊的原理和簡單的模擬可以參考《Effective Java》中的第77條-對(duì)于實(shí)例控制课竣,枚舉類型優(yōu)先于readResolve

  • 類不要實(shí)現(xiàn) Cloneable接口和clone()方法岸梨。

    保證不可以調(diào)用對(duì)象的clone() 函數(shù)來實(shí)例化。

實(shí)現(xiàn)Singleton的三種方法

有多種方法可以實(shí)現(xiàn)Singleton稠氮,雖然每種方法的具體細(xì)節(jié)不一樣曹阔,但是每種方法的目標(biāo)都是相同的:確保類只被實(shí)例化一次。強(qiáng)烈推薦下文描述的第一種方法來實(shí)現(xiàn)Singleton:使用單元素枚舉類型實(shí)現(xiàn)Singleton隔披。

單元素枚舉類型實(shí)現(xiàn)Singleton

Java從1.5發(fā)型版本開始支持通過枚舉(enum)實(shí)現(xiàn)Singleton赃份,下面以enum 實(shí)現(xiàn)一個(gè)Windows 10文件系統(tǒng),這個(gè)文件系統(tǒng)非常的簡陋奢米,只提供了名字和描述兩項(xiàng)基本信息抓韩,具體的代碼如下:

public enum Windows10FileSystem {
    /**
     * singleton file system instance
     */
    INSTANCE("Windows 10", "graphical operating system");

    private String name;
    private String description;

    Windows10FileSystem(String name, String separator) {
        this.name = name;
        this.description = separator;
    }

    public String getBaseInfo() {
        return name + "\t" + description;
    }
}

使用enum 實(shí)現(xiàn)Singleton更加簡潔,無償提供了序列化機(jī)制鬓长,絕對(duì)防止多次實(shí)例化谒拴,即使是在面對(duì)復(fù)雜的序列化和反射攻擊時(shí),這種方法依然絕對(duì)可靠涉波。強(qiáng)烈推薦使用該方法實(shí)現(xiàn)Singleton英上。

導(dǎo)出公有靜態(tài)成員(final 域)實(shí)現(xiàn)Singleton
public class Windows10FileSystem implements Serializable {
    private static final AtomicBoolean FIRST_INSTANTIATION = new AtomicBoolean(true);
    public static final Windows10FileSystem INSTANCE = new Windows10FileSystem("Windows 10","graphical operating system");

    transient private String name;
    transient private String description;

    /**
     * 構(gòu)造器私有化,并防止多次調(diào)用
     * @param name 文件系統(tǒng)名稱
     * @param separator 文件系統(tǒng)描述
     */
    private Windows10FileSystem(String name, String separator) {
        if (FIRST_INSTANTIATION.get()) {
            FIRST_INSTANTIATION.compareAndSet(true,false);
            this.name = name;
            this.description = separator;
        } else {
            throw new UnsupportedOperationException("windows file system can only be instantiated once");
        }
    }

    /**
     * 防止反系列化攻擊
     * @return file system object
     */
    private Object readResolve() {
        return INSTANCE;
    }

    public String getBaseInfo() {
        return name + "\t" + description;
    }
}

static變量的初始化順序參考JLS 8.7

公有靜態(tài)工廠方法實(shí)現(xiàn)Singleton

這種方法相比與導(dǎo)出公有靜態(tài)成員(final 域)實(shí)現(xiàn)Singleton而言只是公有靜態(tài)變量變成了一個(gè)工廠方法啤覆,每次調(diào)用工廠方法都返回同一個(gè)實(shí)例苍日。

private static final Windows10FileSystem INSTANCE = new Windows10FileSystem("Windows 10","graphical operating system");
public static Windows10FileSystem getInstance() {
        return INSTANCE;
    }

兩種實(shí)現(xiàn)Singleton方法的核心都是通過私有化構(gòu)造器來控制類的實(shí)例化。公有域方法的主要優(yōu)勢(shì)在于窗声,類的成員聲明很清楚的表明這個(gè)類是一個(gè)Singleton(可讀性強(qiáng)):公有的靜態(tài)域是final的相恃,所以該域?qū)⒖偸前嗤膶?duì)象。公有域在性能上已經(jīng)不再擁有任何優(yōu)勢(shì)笨觅,現(xiàn)代化的JVM實(shí)現(xiàn)幾乎都能將靜態(tài)工廠方法的調(diào)用內(nèi)聯(lián)化拦耐。靜態(tài)工廠方法的優(yōu)勢(shì)在于耕腾,它提供了更高的靈活性:在不改變API的條件下,我們可以改變?cè)擃愂欠袷荢ingleton的想法杀糯。工廠方法返回該類的唯一實(shí)例幽邓,但是,這可以很容易的被修改火脉,比如修改為每一個(gè)線程返回同一個(gè)實(shí)例牵舵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市倦挂,隨后出現(xiàn)的幾起案子畸颅,更是在濱河造成了極大的恐慌,老刑警劉巖方援,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件没炒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡犯戏,警方通過查閱死者的電腦和手機(jī)送火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來先匪,“玉大人种吸,你說我怎么就攤上這事⊙椒牵” “怎么了坚俗?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長岸裙。 經(jīng)常有香客問我猖败,道長,這世上最難降的妖魔是什么降允? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任恩闻,我火速辦了婚禮,結(jié)果婚禮上剧董,老公的妹妹穿的比我還像新娘幢尚。我一直安慰自己,他們只是感情好送滞,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布侠草。 她就那樣靜靜地躺著,像睡著了一般犁嗅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晤碘,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天褂微,我揣著相機(jī)與錄音功蜓,去河邊找鬼。 笑死宠蚂,一個(gè)胖子當(dāng)著我的面吹牛式撼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播求厕,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼著隆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了呀癣?” 一聲冷哼從身側(cè)響起美浦,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎项栏,沒想到半個(gè)月后浦辨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沼沈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年流酬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片列另。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芽腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出页衙,到底是詐尸還是另有隱情晦嵌,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布拷姿,位于F島的核電站惭载,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏响巢。R本人自食惡果不足惜描滔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踪古。 院中可真熱鬧含长,春花似錦、人聲如沸伏穆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枕扫。三九已至陪腌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诗鸭。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工染簇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人强岸。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓锻弓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蝌箍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子青灼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • JAVA相關(guān)基礎(chǔ)知識(shí) 1、面向?qū)ο蟮奶卣饔心男┓矫?1.抽象: 抽象就是忽略一個(gè)主題中與當(dāng)前目標(biāo)無關(guān)的那些方面妓盲,以...
    yangkg閱讀 667評(píng)論 0 1
  • 1杂拨、面向?qū)ο蟮奶卣饔心男┓矫?1.抽象:抽象就是忽略一個(gè)主題中與當(dāng)前目標(biāo)無關(guān)的那些方面,以便更充分地注意與當(dāng)前目標(biāo)...
    michaelgong閱讀 827評(píng)論 0 1
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,813評(píng)論 0 11
  • 大行其道的知識(shí)體系概念本橙,彭小六扳躬、秋葉老師、泳澄老師等等甚亭,大家都在倡導(dǎo)的建立一個(gè)自己的學(xué)習(xí)系統(tǒng)贷币,學(xué)習(xí)-保存-共享-使...
    xuelinger8401閱讀 245評(píng)論 0 1
  • 有時(shí)候,期待的不是一句“我愛”亏狰, 而是一句“我在”役纹。 未經(jīng)允許, 擅自特別喜歡你暇唾。 只是促脉,終究只是我以為。 一輩子...
    未經(jīng)允許的紅玫瑰閱讀 217評(píng)論 0 1