8中單例模式的寫法

第一種:采用靜態(tài)內(nèi)部類的寫法
public class Singleton {
    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    //默認(rèn)構(gòu)造器弄成私有全景,禁止外部調(diào)用new
    private Singleton() {
    }

    public static final Singleton getInstance(){
        return SingletonHandler.INSTANCE;
    }
}
第二種:餓漢模式顯示單例模式
public class Singleton {
    private static Singleton instance = new Singleton();

    //默認(rèn)構(gòu)造器弄成私有,禁止外部調(diào)用new
    private Singleton() {
    }

    public static Singleton getInstance(){
        return instance;
    }
}
第三種:餓漢變種實(shí)現(xiàn)單例模式
public class Singleton {
    private static Singleton instance = null;

    static {
        instance = new Singleton();
    }

    //默認(rèn)構(gòu)造器弄成私有牵囤,禁止外部調(diào)用new
    private Singleton() {
    }

    public static Singleton getInstance(){
        return instance;
    }
}

以上三種方式都是通過定義靜態(tài)的成員變量爸黄,以保證單例對象可以在類初始化的過程中被實(shí)例化。
以上三種有個(gè)共同特點(diǎn):

  • 構(gòu)造器私有化
  • 靜態(tài)屬性私有化
  • 獲取實(shí)例的方法共有

這其實(shí)是利用了ClassLoader的線程安全機(jī)制揭鳞。ClassLoader的loadClass方法在加載類的時(shí)候使用了synchronized關(guān)鍵字炕贵。

所以, 除非被重寫野崇,這個(gè)方法默認(rèn)在整個(gè)裝載過程中都是線程安全的称开。所以在類加載過程中對象的創(chuàng)建也是線程安全的。

這里就引發(fā)了另一個(gè)問題類加載機(jī)制。需要另一篇文章做支撐鳖轰。

上面這三種情況不是所有的情況下是線程安全的清酥,如果采用反射機(jī)制是可以調(diào)用私有構(gòu)造器的

public class Test {
    public static void main(String[] args) throws Exception {
        Singleton singleton = Singleton.getInstance();
        Singleton instance = Singleton.getInstance();
        System.out.println(singleton == instance);
        
        Constructor<Singleton> singletonConstructor = Singleton.class.getDeclaredConstructor();
        singletonConstructor.setAccessible(true);
        Singleton ref = singletonConstructor.newInstance();
        System.out.println(singleton == ref);
    }
}

通過反射的方式,創(chuàng)建出來的Singleton實(shí)例就不是單例的了脆霎。

還有一個(gè)情況就是序列化之后也不是單例

public class Singleton implements Serializable {
    private static Singleton instance = null;

    static {
        instance = new Singleton();
    }

    //默認(rèn)構(gòu)造器弄成私有总处,禁止外部調(diào)用new
    private Singleton() {
    }

    public static Singleton getInstance(){
        return instance;
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        Singleton instance = Singleton.getInstance();
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.obj"));
        outputStream.writeObject(instance);
        outputStream.flush();
        outputStream.close();

        FileInputStream inputStream = new FileInputStream("test.obj");
        ObjectInputStream inputStream1 = new ObjectInputStream(inputStream);
        Singleton instance1 = (Singleton)inputStream1.readObject();
        inputStream1.close();
        inputStream.close();

        System.out.println(instance == instance1);
    }
}

任何一個(gè)readObject方法,不管是顯式的還是默認(rèn)的睛蛛,它都會返回一個(gè)新建的實(shí)例鹦马,這個(gè)新建的實(shí)例不同于該類初始化時(shí)創(chuàng)建的實(shí)例。
那有沒有方法可以防止反射攻擊和序列化破壞單例模式呢忆肾?

public class  Singleton implements Serializable{
    private static final long serialVersionUID = -4264591697494981165L;

    // 靜態(tài)內(nèi)部類
    private static class SingletonHandler {
        private static final Singleton INSTANCE = new Singleton();
    }

    private Singleton(){
        // 防止反射創(chuàng)建多個(gè)對象
        if(SingletonHandler.INSTANCE!=null){
            throw new RuntimeException("只能實(shí)例化一次");
        }
    }

    public static final Singleton getInstance(){
        return SingletonHandler.INSTANCE;
    }
    // 防止序列化創(chuàng)建多個(gè)對象,這個(gè)方法是關(guān)鍵
    private Object readResolve(){
        return SingletonHandler.INSTANCE;
    }
}

通過這種方式就可以防止反射攻擊和序列化問題

第四種寫法:枚舉類單例
public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

這個(gè)寫法好簡單荸频,枚舉類型其實(shí)是繼承了Enum抽象類的,而抽象類中是沒有無參構(gòu)造器客冈,所以不管你反射的時(shí)候調(diào)用無參構(gòu)造器旭从,還是調(diào)用父類的有參構(gòu)造器都會拋出異常,這樣就避免了反射攻擊场仲。也解決了序列化問題和悦。
參考:https://www.cnblogs.com/chiclee/p/9097772.html

枚舉類是JDK1.5才出現(xiàn)的,那之前的程序員面對反射攻擊和序列化問題是怎么解決的呢渠缕?其實(shí)就是像Enum源碼那樣解決的鸽素,只是現(xiàn)在可以用enum可以使我們代碼量變的極其簡潔了。
Joshua Bloch說的“單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法”

這里序列化Serializable 亦鳞,實(shí)現(xiàn)了這個(gè)接口之后馍忽,所以的方法和屬性都自動(dòng)序列化了,有時(shí)候我們不需要對所有的屬性都進(jìn)行序列化燕差,所以就引出了transient關(guān)鍵字遭笋。打個(gè)比方,如果一個(gè)用戶有一些敏感信息(如密碼徒探,銀行卡號等)瓦呼,為了安全起見,不希望在網(wǎng)絡(luò)操作(主要涉及到序列化操作测暗,本地序列化緩存也適用)中被傳輸吵血,這些信息對應(yīng)的變量就可以加上transient關(guān)鍵字。換句話說偷溺,這個(gè)字段的生命周期僅存于調(diào)用者的內(nèi)存中而不會寫到磁盤里持久化蹋辅。

1.一旦變量被transient修飾,變量將不再是對象持久化的一部分挫掏,該變量內(nèi)容在序列化后無法獲得訪問。

2.transient關(guān)鍵字只能修飾變量,而不能修飾方法和類褒傅。注意弃锐,本地變量是不能被transient關(guān)鍵字修飾的。變量如果是用戶自定義類變量殿托,則該類需要實(shí)現(xiàn)Serializable接口霹菊。

3.被transient關(guān)鍵字修飾的變量不再能被序列化,一個(gè)靜態(tài)變量不管是否被transient修飾支竹,均不能被序列化旋廷。

參考:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html

所以第四種單例模式是線程安全的。原因就是枚舉其實(shí)底層是依賴Enum類實(shí)現(xiàn)的礼搁,這個(gè)類的成員變量都是static類型的饶碘,并且在靜態(tài)代碼塊中實(shí)例化的,和餓漢有點(diǎn)像馒吴, 所以他天然是線程安全的扎运。

第五種雙重檢查的方式(俗稱Double Check方式)實(shí)現(xiàn)單例模式
第六種使用同步代碼塊(效率很低)
public class Singleton {  
    private static Singleton instance=null;  
     
    private Singleton() {  
         
    }  
     
    public synchronized static Singleton getInstance(){  
        if (instance == null) {  
            instance = new Singleton();  
        }  
         
        return instance;  
    }  
}
第七種使用CAS(非阻塞方式也叫樂觀鎖)實(shí)現(xiàn)單例模式
public class  Singleton{
    private static final AtomicReference<Singleton> INSTANCE= new AtomicReference<Singleton>();

    private Singleton(){}

    public static final Singleton getInstance(){
        for(;;){
            Singleton current = INSTANCE.get();
            if(current != null){
                return current;
            }
            current = new Singleton();
            if(INSTANCE.compareAndSet(null,current)){
                return current;
            }
        }
    }
}

用CAS的好處在于不需要使用傳統(tǒng)的鎖機(jī)制來保證線程安全,CAS是一種基于忙等待的算法饮戳,依賴底層硬件的實(shí)現(xiàn)豪治,相對于鎖它沒有線程切換和阻塞的額外消耗,可以支持較大的并行度扯罐。

CAS的一個(gè)重要缺點(diǎn)在于如果忙等待一直執(zhí)行不成功(一直在死循環(huán)中)负拟,會對CPU造成較大的執(zhí)行開銷。
另外篮赢,代碼中,如果N個(gè)線程同時(shí)執(zhí)行到 singleton = new Singleton();的時(shí)候琉挖,會有大量對象被創(chuàng)建启泣,可能導(dǎo)致內(nèi)存溢出。

那這里就引出了另外一個(gè)問題就是CAS原理

什么是CAS示辈? CAS:Compare and Swap寥茫,即比較再交換。

jdk5增加了并發(fā)包java.util.concurrent.*,其下面的類使用CAS算法實(shí)現(xiàn)了區(qū)別于synchronouse同步鎖的一種樂觀鎖矾麻。JDK 5之前Java語言是靠synchronized關(guān)鍵字保證同步的纱耻,這是一種獨(dú)占鎖,也是是悲觀鎖险耀。

參考:http://www.reibang.com/p/ab2c8fce878b

第八種ThreadLocal(以空間換時(shí)間方式)實(shí)現(xiàn)單例模式

ThreadLocal會為每一個(gè)線程提供一個(gè)獨(dú)立的變量副本弄喘,從而隔離了多個(gè)線程對數(shù)據(jù)的訪問沖突。對于多線程資源共享的問題甩牺,同步機(jī)制(synchronized)采用了“以時(shí)間換空間”的方式蘑志,而ThreadLocal采用了“以空間換時(shí)間”的方式。

同步機(jī)制僅提供一份變量,讓不同的線程排隊(duì)訪問急但,而ThreadLocal為每一個(gè)線程都提供了一份變量澎媒,因此可以同時(shí)訪問而互不影響。

public class  Singleton{
    private static final ThreadLocal<Singleton> singleton = new ThreadLocal<Singleton>(){
        @Override
        protected Singleton initialValue() {
            return new Singleton();
        }
    };

    private Singleton(){}

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

ThreadLocal 擴(kuò)展知識:參考http://www.reibang.com/p/3c5d7f09dfbd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末波桩,一起剝皮案震驚了整個(gè)濱河市戒努,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镐躲,老刑警劉巖储玫,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匀油,居然都是意外死亡缘缚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門敌蚜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桥滨,“玉大人,你說我怎么就攤上這事弛车∑朊剑” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵纷跛,是天一觀的道長喻括。 經(jīng)常有香客問我,道長贫奠,這世上最難降的妖魔是什么唬血? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮唤崭,結(jié)果婚禮上拷恨,老公的妹妹穿的比我還像新娘。我一直安慰自己谢肾,他們只是感情好腕侄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芦疏,像睡著了一般冕杠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酸茴,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天分预,我揣著相機(jī)與錄音,去河邊找鬼薪捍。 笑死噪舀,一個(gè)胖子當(dāng)著我的面吹牛魁淳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播与倡,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼界逛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纺座?” 一聲冷哼從身側(cè)響起息拜,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎净响,沒想到半個(gè)月后少欺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馋贤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年赞别,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片配乓。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仿滔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出犹芹,到底是詐尸還是另有隱情崎页,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布腰埂,位于F島的核電站飒焦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屿笼。R本人自食惡果不足惜牺荠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驴一。 院中可真熱鬧休雌,春花似錦、人聲如沸蛔趴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽孝情。三九已至,卻和暖如春洒嗤,著一層夾襖步出監(jiān)牢的瞬間箫荡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工渔隶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留羔挡,地道東北人洁奈。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像绞灼,于是被迫代替她去往敵國和親利术。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354