KandQ:單例模式的七種寫法及其相關問題解析

来源:http://www.cnblogs.com/MyStringIsNotNull/archive/2017/12/10/8018130.html
-Advertisement-
Play Games

設計模式中的單例模式可以有7種寫法,這7種寫法有各自的優點和缺點: 代碼示例(java)及其分析如下: 一、懶漢式 優點:   不是馬上就初始化的,當需要使用的時候才進行初始化(即是lazy loading) 缺點:   在併發情況下是線程不安全的 二、懶漢式 ...


設計模式中的單例模式可以有7種寫法,這7種寫法有各自的優點和缺點:

代碼示例(java)及其分析如下:

一、懶漢式

public class Singleton
{
    private static Singleton singleton;

    private Singleton()
    {
    }

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

優點:

  不是馬上就初始化的,當需要使用的時候才進行初始化(即是lazy loading)

缺點:

  在併發情況下是線程不安全的

二、懶漢式線程安全版

public class Singleton
{
    private static Singleton singleton;

    private Singleton()
    {
    }

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

優點:

  不是類載入之後就進行初始化的,當需要使用的時候才進行初始化(即是lazy loading),且為線程安全的

缺點:

  效率低,加了synchronized進行同步之後,效率上有所降低

三、餓漢式

public class Singleton
{
    private static Singleton singleton = new Singleton();

    private Singleton()
    {
    }

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

  這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。其一個明顯的好處就是是線程安全的

四、餓漢式的變種寫法

public class Singleton
{
    private static Singleton singleton;
    static
    {
        singleton = new Singleton();
    }

    private Singleton()
    {
    }

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

  其會在類載入的時候就進行載入。和上面的餓漢式的寫法優缺點相同

五、靜態內部類方式

public class Singleton
{
    private Singleton()
    {
    }

    private static class SingletonHolder
    {
        private static final Singleton singleton = new Singleton();
    }

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

  這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別): 第三種和第四種方式是只要Singleton類被裝載了,那麼instance就會被實例化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而實例化instance。想象一下,如果實例化instance很消耗資源,我想讓他延遲載入,另外一方面,我不希望在Singleton類載入時就實例化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被載入,那麼這個時候實例化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。

六、採用枚舉方式

public enum Singletons
{
    INSTANCE;
    // 此處表示單例對象裡面的各種方法
    public void Method()
    {
    }
}

  Effective Java作者Josh Bloch提倡使用枚舉的方式去實現單例模式。因為它不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象,同時寫法簡單。對於枚舉方式創建單例,為何可以避免多線程的同步以及防止反序列化重新創建新的對象這個原因,詳見相關博文:K:枚舉的線程安全性及其序列化問題

七、雙重校驗鎖

public class Singleton
{
    private volatile static Singleton singleton;

    private Singleton()
    {
    }

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

  對於雙重校驗鎖,其是對懶漢式線程安全版的改進,其目的在於減少同步所用的開銷。對singleton進行兩次判null檢查,一次是在同步塊外,一次是在同步塊內。為什麼在同步塊內還要再檢驗一次?因為可能會有多個線程一起進入同步塊外的 if,如果在同步塊內不進行二次檢驗的話就會生成多個實例。

  對singleton變數使用volatile關鍵字的原因是,instance = new Singleton()這句,並非是一個原子操作,事實上在 JVM中這句話大概做了下麵3件事情:

  1. 給 instance 分配記憶體
  2. 調用 Singleton的構造函數來初始化成員變數
  3. 將instance對象指向分配的記憶體空間(執行完這步instance就為非null了)

  但是在 JVM的即時編譯器中存在指令重排序的優化。也就是說上面的第二步和第三步的順序是不能保證的,最終的執行順序可能是1-2-3也可能是1-3-2。如果是後者,則在3執行完畢、2未執行之前,被線程二搶占了,這時instance已經是非null了(但卻沒有初始化),所以線程二會直接返回instance,然後使用,然後順理成章地jvm就會報錯。

  有些人認為使用volatile的原因是可見性,也就是可以保證線程在本地不會存有instance的副本,每次都是去主記憶體中讀取。但其實是不對的。使用volatile的主要原因是其另一個特性:禁止指令重排序優化。也就是說,在volatile變數的賦值操作後面會有一個記憶體屏障(生成的彙編代碼上),讀操作不會被重排序到記憶體屏障之前。比如上面的例子,取操作必須在執行完1-2-3之後或者1-3-2之後,不存在執行到1-3然後取到值的情況。從「先行發生原則」(即happen-before)的角度理解的話,就是對於一個volatile變數的寫操作都先行發生於後面對這個變數的讀操作(這裡的“後面”是時間上的先後順序)。

  對於第一種和第二種寫法,實際上其可以歸類為懶漢式這一種寫法,對於第三種和第四種,其也可以歸為餓漢式這一種寫法。為此,一般單例都是五種寫法。懶漢,餓漢,雙重校驗鎖,枚舉和靜態內部類

  對於單例模式,其有兩個問題需要註意:

  1. 如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的實例。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的實例。

  2. 如果Singleton實現了java.io.Serializable介面,那麼這個類的實例就可能被序列化和複原。不管怎樣,如果你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的實例。

對第一個問題修複的辦法是:

private static Class getClass(String classname)throws ClassNotFoundException
{
    // 獲取當前執行線程的上下文類載入器
    ClassLoader classLoader = Thread.currentThread()
            .getContextClassLoader();
    if (classLoader == null)
        classLoader = Singleton.class.getClassLoader();
    return (classLoader.loadClass(classname));
}

對第二個問題修複的辦法是:

class Singletones implements java.io.Serializable
{
    private static Singletones INSTANCE = new Singletones();

    private Singletones()
    {
    }

    public static Singletones getInstance()
    {
        return INSTANCE;
    }

    /*
     * 我們反序列化後獲得的並不是原來的對象,而是經過重構的新的對象實例。
     * ObjectInputStream對象在反序列化的時候,會在從I/O流中讀取對象時
     * ,調用readResolve()方法。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中重構的對象。
     */
    private Object readResolve()
    {
        return Singletones.getInstance();
    }
}

原因詳見博文:K:java中序列化的兩種方式—Serializable或Externalizable

回到目錄|·(工)·)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 時間:2017年12月10日 21:52:59 用途:此文章用於個人總結 css筆記1.CSS全稱為"層疊樣式表(Cascading Style Sheets)"2.CSS樣式的語法: 選擇符{屬性:值}3.CSS樣式代碼插入的三種形式:內聯/嵌入/外部 內聯式:就是把代碼寫在html標簽的行間樣式 ...
  • 萬事開頭難,一個好的Hello World程式可以節省我們好多的學習時間,幫助我們快速入門。Hello World程式之所以是入門必讀必會,就是因為其代碼量少,簡單易懂。但我覺得,還應該做到功能豐富,涉及的知識點多。這樣才是一個好的初學者入門指引程式。 之所以選擇Vue,不僅因為其流行,還因為其輕量 ...
  • 關於ES6模塊化 歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個大程式拆分成互相依賴的小文件,再用簡單的方法拼裝起來。其他語言都有這項功能,比如 Ruby 的require、Python 的import,甚至就連 CSS 都有@import,但是 JavaScript 任 ...
  • 寫在前面 這個文章,展現的是一個實現Promise的思路,以及如何發現和處理問題的情境。 從現有的Promise分析 如果我們想要自己實現一個簡單的 ,那現有規範規定的 肯定是我們最好的參照。 我們先看下 怎麼使用: 來看下返回的 是什麼,以及它的結構是怎麼樣的: 再進行一些具體操作 從Promis ...
  • 1.(webpack)vue-cli構建的項目如何設置每個頁面的title 在路由里每個都添加一個meta 鉤子函數: 在main.js中添加如下代碼 2.vue項目中使用axios上傳圖片等文件 首先安裝axios:1.利用npm安裝npm install axios –save2.利用bower ...
  • 對於平時項目開發中,經常要展示圖片。什麼水平居中顯示,垂直居中顯示,水平或垂直居中顯示...我們的髮際線就是這樣往後退的。 接下來要講的就是對於各種圖片佈局的css實現(這裡針對的是img標簽的不會使用到background) 1.最簡單的水平居中 .exa1{ width: 500px; heig ...
  • 1、安裝 nvm curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash 安裝成功預設將會在用戶文件夾中生成一個隱藏的 .nvm 文件 顯示隱藏文件:defaults write com. ...
  • DOM(文檔對象模型)是針對HTML和XML文檔的一個API,描繪了一個層次化的節點樹,允許開發人員添加、刪除和修改頁面的某一部分。 HTML DOM 樹形結構如下: 1.Node方面 1.1 節點類型 確定節點類型,相容的方法是將nodeType屬性與數字值進行比較,如下所示: if(someNo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...