創(chuàng)建型設計模式(一)單例

單例模式是指一個類確保其只擁有一個實例對象瞳遍,且此實例對象由類自己創(chuàng)建并提供方法為整個系統(tǒng)提供這個實例的訪問炭菌。

一滔岳、特點

  • 實現(xiàn)單例模式的類只有唯一一個實例對象
  • 實現(xiàn)單例模式的類必須自己創(chuàng)建此實例對象
  • 實現(xiàn)單例模式的類必須向整個系統(tǒng)提供訪問此實例對象的方法

二沥潭、代碼實現(xiàn)

1. 餓漢式單例

單例類在加載時即實例化唯一對象。

package singleton;

public class HungrySingleton {
    
    private static final HungrySingleton singleton = new HungrySingleton();
    
    private HungrySingleton() {}
    
    public static HungrySingleton getSingleton() {
        return singleton;
    }
}

2. 懶漢式單例

單例類在系統(tǒng)首次調(diào)用其提供的訪問實例對象的方法時實例化唯一對象买窟。

package singleton;

import java.util.Objects;

public class LazySingleton {

    private static LazySingleton singleton;

    private LazySingleton() {}

    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

相比于餓漢式單例,懶漢式單例更加合理薯定,只有需要時才創(chuàng)建具體的實例對象而非類加載時便實例化此對象始绍。為說明此問題請看以下代碼:

package singleton;

public class HungrySingleton {
    
    private static final HungrySingleton singleton = new HungrySingleton();
    
    private HungrySingleton() {}
    
    public static HungrySingleton getSingleton() {
        return singleton;
    }
    
    public static void methodA() {
        System.out.println("Provide other service");
    }
}

在實現(xiàn)餓漢式單例的類中新增了一個方法methodA,該方法可以提供服務话侄,當methodA被首次調(diào)用時亏推,HungrySingleton便被加載并實例化了靜態(tài)屬性singleton,但實際上此實例對象尚未被使用年堆,即使后續(xù)系統(tǒng)中一直不使用singleton實例對象吞杭,此實例對象也會常駐內(nèi)存。

3. 加鎖實現(xiàn)懶漢式單例

第 2 部分懶漢式單例代碼中存在一個問題嘀韧,沒有考慮多線程并發(fā)場景篇亭,當多個線程同時訪問getSingleton方法時可能實例化多個對象,但最終只有一個對象被賦給靜態(tài)屬性singleton锄贷,其它對象只能等待垃圾回收译蒂,這不免造成了系統(tǒng)資源浪費。處理線程并發(fā)問題最簡單的方法是通過加鎖實現(xiàn)線程同步谊却。

package singleton;

import java.util.Objects;

public class LazySingleton {

    private static LazySingleton singleton;

    private LazySingleton() {}

    public static synchronized LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            singleton = new LazySingleton();
        }
        return singleton;
    }
}

以上代碼中對懶漢式單例類提供的訪問唯一對象實例的方法添加了一個內(nèi)部鎖(當然可以將synchronized同步塊加在方法體內(nèi)柔昼,或使用顯示鎖方案替換內(nèi)部鎖),這樣就保證同時只有一個線程能夠訪問getSingleton方法炎辨,就不可能出現(xiàn)多個線程同時實例化對象的場景捕透。

4. 雙重鎖單例

加鎖實現(xiàn)懶漢式單例雖然解決了線程同步問題,但還存在缺陷,即singleton在實例化成功后乙嘀,后續(xù)如果有多個線程同時調(diào)用getSingleton方法試圖獲取singleton的引用末购,這些線程仍需串行執(zhí)行,由此造成性能瓶頸虎谢。解決此問題的方法是采用雙重鎖單例盟榴。

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

雙重鎖解決了多線程獲取單例對象的性能問題,只有對象第一次實例化時會使用線程同步婴噩,一旦實例化成功擎场,后續(xù)調(diào)用getSingleton獲取對象實例都無需再次同步執(zhí)行。

5. 增加關鍵字volatile

雙重鎖單例仍然存在發(fā)生異常的可能几莽。原因是迅办,為了提升執(zhí)行效率,Java 虛擬機可能會對synchronized塊中代碼進行重排序章蚣。雖然singleton = new LazySingleton();看似一個語句站欺,實際執(zhí)行過程中會分為幾個步驟完成:

  1. 分配內(nèi)存區(qū)域
  2. 在已分配內(nèi)存區(qū)域進行對象實例化
  3. 將實例化對象引用賦值給singleton

Java 虛擬機在執(zhí)行過程中可能會對上訴步驟 2 和 3 進行重排序,即先將內(nèi)存區(qū)域引用賦值給singleton然后再實現(xiàn)對象實例化究驴,如果在對象實例化前一個線程調(diào)用getSingleton方法镊绪,則此時singleton不為null,但實際上對象實例化并未完成洒忧,該線程可能會拿到一個未實例化完的對象引用調(diào)用其提供的實例方法蝴韭,很明顯因為對象并未完成實例化,所以此時的實例方法調(diào)用肯定會出現(xiàn)異常熙侍。

為解決此問題榄鉴,可以在靜態(tài)屬性singleton上加上關鍵字volatile,這會阻止 Java 虛擬機的重排序行為蛉抓,保證對象是先實例化后再賦值給singleton庆尘。

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static volatile LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }
}

6. 使用一個局部變量實例化對象,然后將此局部變量賦值給 singleton

除使用關鍵字volatile外巷送,還可以通過局部變量實例化對象賦值的方法解決指令重排帶來的問題驶忌。

package singleton;

import java.util.Objects;

public class LazySingleton {
    
    private static LazySingleton singleton;
    
    private LazySingleton() {}
    
    public static LazySingleton getSingleton() {
        if (Objects.isNull(singleton)) {
            synchronized (LazySingleton.class) {
                if (Objects.isNull(singleton)) {
                    LazySingleton temp = new LazySingleton();
                    singleton = temp;
                }
            }
        }
        return singleton;
    }
}

7. 使用靜態(tài)內(nèi)部類實現(xiàn)懶漢式單例

還有一種更加簡單的單例實現(xiàn)方法,利用了 Java 靜態(tài)內(nèi)部類的機制笑跛,當 LazySingleton 被加載時并不會觸發(fā)其靜態(tài)內(nèi)部類 Holder 的加載付魔,只有首次調(diào)用 getSingleton 方法時才會加載靜態(tài)內(nèi)部類 Holder,又因 Holder 的靜態(tài)屬性 singletonfinal飞蹂,所以只會被實例化一次几苍。

package singleton;

public class LazySingleton {
    
    private LazySingleton() {}
    
    private static final class Holder {
        private static final LazySingleton singleton = new LazySingleton();
    }
    
    public static LazySingleton getSingleton() {
        return Holder.singleton;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市陈哑,隨后出現(xiàn)的幾起案子妻坝,更是在濱河造成了極大的恐慌伸眶,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刽宪,死亡現(xiàn)場離奇詭異厘贼,居然都是意外死亡,警方通過查閱死者的電腦和手機纠屋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門涂臣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人售担,你說我怎么就攤上這事∈鸹裕” “怎么了族铆?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哭尝。 經(jīng)常有香客問我哥攘,道長,這世上最難降的妖魔是什么材鹦? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任逝淹,我火速辦了婚禮,結(jié)果婚禮上桶唐,老公的妹妹穿的比我還像新娘栅葡。我一直安慰自己,他們只是感情好尤泽,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布欣簇。 她就那樣靜靜地躺著,像睡著了一般坯约。 火紅的嫁衣襯著肌膚如雪熊咽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天闹丐,我揣著相機與錄音横殴,去河邊找鬼。 笑死卿拴,一個胖子當著我的面吹牛衫仑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巍棱,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼惑畴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了航徙?” 一聲冷哼從身側(cè)響起如贷,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后杠袱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尚猿,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年楣富,在試婚紗的時候發(fā)現(xiàn)自己被綠了凿掂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡纹蝴,死狀恐怖庄萎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情塘安,我是刑警寧澤糠涛,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站兼犯,受9級特大地震影響忍捡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜切黔,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一砸脊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纬霞,春花似錦凌埂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至绢陌,卻和暖如春挨下,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脐湾。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工臭笆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秤掌。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓愁铺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闻鉴。 傳聞我的和親對象是個殘疾皇子茵乱,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

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