Singleton 單例模式

動機

有些情況下,一個類只能有一個實例是很重要的仓坞。比如說颜说,在操作系統(tǒng)中只能有一個窗口管理器的(文件系統(tǒng)或打印機程序)购岗。通常, 單實例用于對內(nèi)部或外部資源的集中式管理门粪,同時它們提供一個訪問其自身的全局入口喊积。

單例模式是最簡單的設計模式之一。它只涉及到一個負責實例化它自己的類玄妈,這個類保證其只創(chuàng)建一個實例(私有化構(gòu)造函數(shù))乾吻;同時該類提供一個訪問該實例的全局入口。這樣拟蜻,程序各處都使用同一實例绎签,不會每次都直接調(diào)用構(gòu)造函數(shù)。

目的

  • 確保一個類只創(chuàng)建一個實例
  • 提供訪問該單一實例的全局入口

實現(xiàn)

具體實現(xiàn)涉及 Singleton 類的一個靜態(tài)私有成員酝锅,一個私有構(gòu)造函數(shù)和一個共有方法返回該靜態(tài)私有成員的引用诡必。

單例實現(xiàn) uml

單例模式定義一個 getInstance 方法來暴露供客戶端訪問的單一實例。getInstance() 負責在單一實例還沒被創(chuàng)建的時候創(chuàng)建它并返回該實例搔扁。

class Singleton{
    private static Singleton instance;
    private Singleton(){
        // ...
    }

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

    public void doSomething(){
        //...
    }
}

你可以發(fā)現(xiàn)上面的代碼 getInstance 方法確保只創(chuàng)建一個類實例爸舒。不能從類的外部訪問構(gòu)造函數(shù)以保證只能通過 getIntance 方法來創(chuàng)建類實例。

getInstance 方法也作為對象唯一的全局訪問入口稿蹲,可以像下面這樣使用:

Singleton.getInstance().doSomething();

適用場景 & 例子

根據(jù)定義扭勉,單例模式的使用場景應該是一個類必須只有一個實例,并且必須從一個全局入口訪問這個實例苛聘。以下幾個是使用單例模式的真實案例:

  • 日志類 Logger Classes
    單例模式被用于日志類的設計中涂炎。 這些日志類通常以單例來實現(xiàn),并且在應用各組件中提供一個全局的日志記錄入口设哗,執(zhí)行日志記錄操作時就不用每次都創(chuàng)建對象了璧尸。
  • 配置類 Configuration Classes
    使用單例模式設計為應用提供配置的類。通過將配置類實現(xiàn)為單例熬拒,不單單提供全局訪問入口爷光,我們還可以將這個實例作為緩存對象。當實例化類的時候(讀取值)澎粟,單例會將值保持在其內(nèi)部結(jié)構(gòu)中蛀序。如果配置是從數(shù)據(jù)庫或者文件中讀取,這樣就不用每次使用配置參數(shù)時都要去重新載入值了活烙。
  • 共享地訪問資源
    單例模式可以用于設計需要串行運行的應用徐裸。假設應用中有許多在多線程環(huán)境中運行的類,這些類需要串行地執(zhí)行操作啸盏。在這種情況下重贺, 帶有 synchronized 方法的單例實例就可以用來管理這些串行操作。
  • 單例實現(xiàn)的工廠
    假設我們設計一個執(zhí)行于多線程環(huán)境下的應用,其中有一個用于生成帶有id的新對象(賬戶气笙,客戶次企,網(wǎng)站,地址等對象)潜圃。如果這工廠類在2個不同的線程中實例化2次缸棵,那么就可能出現(xiàn)id重疊的2個不同對象。如果我們將這個 Factory 實現(xiàn)為一個單例就可以避免這個問題谭期。通常將 抽象工廠工廠方法單例模式 一起使用堵第。

特定的問題和實現(xiàn)

為了在多線程下使用,線程安全的實現(xiàn)

一個健壯的單例實現(xiàn)應該在任何情況下都能正常工作隧出。這就是為什么我們要確保多線程使用時它也能正常工作的原因踏志。如前面例子的單例確保讀寫操作都是同步的,它可用于多線程應用中胀瞪。

一狰贯、 使用雙重鎖定機制實現(xiàn)延遲初始化(懶漢)

上面代碼中展示的標準實現(xiàn)是一種線程安全的實現(xiàn),但它不是最好的線程安全實現(xiàn)赏廓,因為當我們考慮性能時涵紊,同步操作的開銷比較大。我們能看出同步的 getInstance 在實例已經(jīng)創(chuàng)建后并不需要再進行同步幔摸。如果我們發(fā)現(xiàn)實例已經(jīng)創(chuàng)建摸柄,我們只需返回這個實例,而不需要使用任何同步代碼塊既忆。這個優(yōu)化在于在非同步代碼塊中檢查實例是否為 null驱负, 再在同步代碼塊中檢驗是否 null 并且創(chuàng)建實例。這稱為雙重鎖定機制患雇。

在這種情況下跃脊,單例實例在第一次調(diào)用 getInstance() 方法的時候創(chuàng)建。這就叫延遲初始化苛吱,并且它確保這個單例的實例只在需要的時候創(chuàng)建酪术。

// 使用雙重鎖定機制的延遲初始化
class Singleton{
    private static volatile Singleton instance; 

    private Singleton(){}

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

    public void doSomething(){
         // ...
    }
}

關(guān)于為什么要加 volatile 可以參考下 https://www.ibm.com/developerworks/cn/java/j-jtp06197.html

二、 使用靜態(tài)字段實現(xiàn)預先加載 (餓漢)

由于以下實現(xiàn)中單例實例被聲明為靜態(tài)成員了翠储,在類加載的時候就實例化了而不是第一次使用它的時候绘雁。這就是為什么我們不再需要同步代碼了。 類只加載一次保證實例的唯一性援所。

class Singleton{
    private static Singleton instance = new Singleton();

    private Singleton() {
         //...
    }

    public static Singleton getInstance(){
        return instance;
    }

    public void doSomething(){
        //...
    }
}

protected constructor

可以使用 protected 訪問修飾符的構(gòu)造函數(shù)來授權(quán)給子類庐舟。但是這種技術(shù)有2個缺陷,使得單例的繼承不切實際:

  • 首先住拭,如果構(gòu)造函數(shù)是 protected挪略, 意味著這個類可以被同一個包內(nèi)的其他類實例化历帚。可能的措施是隔離單例類杠娱。
  • 其次挽牢,要使用派生類,所有的 getInstance 調(diào)用都得從現(xiàn)有代碼中的 Singleton.getInstance() 改為 NewSingleton.getInstance()

如果多個 classloader 訪問同一個單例類墨辛,會有多個單例實例

如果一個類(相同類名,相同包名)被2個不同的 classloader 加載趴俘,那么他們代表內(nèi)存中2個不同的類睹簇。

序列化

如果單例類實現(xiàn)了 java.io.Serializable 接口,當單例實例被序列化和反序列化多次時寥闪,就會創(chuàng)建多個單例類實例太惠。為了避免這種情況,必須實現(xiàn) readResolve 方法疲憋。 參考下 Serializable () 和 readResolve 方法的說明凿渊。

抽象工廠工廠方法 實現(xiàn)為單例

在一些特定的場景下工廠必須是唯一的。存在2個工廠的話缚柳,創(chuàng)建對象時會有意料之外的影響埃脏。為了確保工廠的唯一性,它要實現(xiàn)成單例秋忙。這樣做之后我們也避免了使用前的工廠實例化彩掐。

Hot Spot:

  • 多線程: 當單例運行于多線程應用時,必須格外小心
  • 序列化: 當單例類實現(xiàn)了 Serializable 接口灰追,它們必須實現(xiàn) readResolve 方法以避免 2 個不同的對象
  • Classloaders 如果單例類被2個不同的類加載器加載堵幽,我們將得到2個不同類,一個類加載器一個
  • 由類名表示的全局訪問入口:單例類的實例通過類名來獲取弹澎。乍一看朴下,這樣很容易訪問實例,但這不是很靈活苦蒿。如果我們要替換這個單例類殴胧,就要修改代碼中所有的引用。

jdk 中的使用

**
 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.
 *
 * @author  unascribed
 * @see     java.lang.Runtime#getRuntime()
 * @since   JDK1.0
 */
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

    // ...
}

more
示例代碼:https://github.com/minorpoet/design-patterns/tree/master/Singleton
classloader: http://ifeve.com/classloader/
volatile: http://www.reibang.com/p/3893fb35240f
double-check-lock is broken: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末佩迟,一起剝皮案震驚了整個濱河市溃肪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌音五,老刑警劉巖惫撰,帶你破解...
    沈念sama閱讀 218,607評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異躺涝,居然都是意外死亡厨钻,警方通過查閱死者的電腦和手機扼雏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夯膀,“玉大人诗充,你說我怎么就攤上這事∮战ǎ” “怎么了蝴蜓?”我有些...
    開封第一講書人閱讀 164,960評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俺猿。 經(jīng)常有香客問我茎匠,道長,這世上最難降的妖魔是什么押袍? 我笑而不...
    開封第一講書人閱讀 58,750評論 1 294
  • 正文 為了忘掉前任诵冒,我火速辦了婚禮,結(jié)果婚禮上谊惭,老公的妹妹穿的比我還像新娘汽馋。我一直安慰自己,他們只是感情好圈盔,可當我...
    茶點故事閱讀 67,764評論 6 392
  • 文/花漫 我一把揭開白布豹芯。 她就那樣靜靜地躺著,像睡著了一般驱敲。 火紅的嫁衣襯著肌膚如雪告组。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,604評論 1 305
  • 那天癌佩,我揣著相機與錄音木缝,去河邊找鬼。 笑死围辙,一個胖子當著我的面吹牛我碟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姚建,決...
    沈念sama閱讀 40,347評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼矫俺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了掸冤?” 一聲冷哼從身側(cè)響起厘托,我...
    開封第一講書人閱讀 39,253評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎稿湿,沒想到半個月后铅匹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡饺藤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,893評論 3 336
  • 正文 我和宋清朗相戀三年包斑,在試婚紗的時候發(fā)現(xiàn)自己被綠了流礁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,015評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡罗丰,死狀恐怖神帅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萌抵,我是刑警寧澤找御,帶...
    沈念sama閱讀 35,734評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站绍填,受9級特大地震影響霎桅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沐兰,卻給世界環(huán)境...
    茶點故事閱讀 41,352評論 3 330
  • 文/蒙蒙 一哆档、第九天 我趴在偏房一處隱蔽的房頂上張望蔽挠。 院中可真熱鬧乡话,春花似錦考余、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至派歌,卻和暖如春埃撵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氢拥。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評論 1 270
  • 我被黑心中介騙來泰國打工蚌铜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嫩海。 一個月前我還...
    沈念sama閱讀 48,216評論 3 371
  • 正文 我出身青樓冬殃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叁怪。 傳聞我的和親對象是個殘疾皇子审葬,可洞房花燭夜當晚...
    茶點故事閱讀 44,969評論 2 355

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