單例模式正確使用方式

来源:https://www.cnblogs.com/winqi/archive/2019/08/23/11402986.html
-Advertisement-
Play Games

這次分享我們就來談談單例模式的使用,其實在本公眾號設計模式的第一篇分享就是單例模式,為什麼又要討論單例模式了?主要是那篇文章談的比較淺,只對單例模式的主要思想做了一個分享,這篇文章會從多個方面去分享單例模式的使用,下麵進入正題。 使用Java做程式的小伙伴都知道單例,尤其是使用spring框架做項目 ...


這次分享我們就來談談單例模式的使用,其實在本公眾號設計模式的第一篇分享就是單例模式,為什麼又要討論單例模式了?主要是那篇文章談的比較淺,只對單例模式的主要思想做了一個分享,這篇文章會從多個方面去分享單例模式的使用,下麵進入正題。

使用Java做程式的小伙伴都知道單例,尤其是使用spring框架做項目的,大家都知道spring框架管理類預設都是單例模式的,並且是線程安全的。那麼如果保證一個類只被初始化一次且線程安全了?帶著這個問題我們開始這篇文章。

在剛接觸設計模式的時候,大多數人首先接觸到的第一個設計模式就是單例模式,當然大多數人都只停留在單例模式,在平時的開發中用的少之又少,這篇文章會幫助大家從多個方面理解單例模式,此文代碼較多,如果對單例模式概念不是很清楚的小伙伴可以看另一篇文章《設計模式之單例模式》。

1. 懶漢模式

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton01</p >
 * <p>Description: 懶漢模式  單例實例在第一次使用時進行創建 </p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:41
 * @Version: 1.0
 */
public class Singleton01 {

    private Singleton01() {

    }

    //單例對象
    private static Singleton01 instance = null;

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:43
     * @return: Singleton01
     */
    public static Singleton01 getInstance() {
        if (instance == null) {
            instance = new Singleton01();
        }
        return instance;
    }
}

上述代碼在單線程下是沒有問題的,但是在多線程情況下是線程不安全的,出現問題的代碼主要是在下麵這段代碼:

2. 餓漢模式

為瞭解決上面懶漢模式線程不安全問題,我們可以使用餓漢模式創建類實例。餓漢模式是單例實例在類裝載時進行創建,代碼如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton02</p >
 * <p>Description: 餓漢模式</p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:53
 * @Version: 1.0
 */
public class Singleton02 {

    private Singleton02() {
    }

    //單例對象
    private static Singleton02 instance = new Singleton02();

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:55
     * @return: Singleton02
     */
    public static Singleton02 getInstance() {
        return instance;
    }
}

餓漢模式問題

餓漢模式雖然是線程安全的,但是在實際開發中,會有一些大家需要註意的問題。

第一點:餓漢模式是在類裝載的時候就創建的,所以如果類的構造方法裡面很多複雜的處理,類裝載就會比較慢,影響程式性能;

第二點:餓漢模式創建的示例,不管在項目其他地方有沒有使用,類的實例已經創建,如果不使用,會造成資源的浪費。

基於以上兩點,大家在使用餓漢模式的時候需要註意實際的使用場景。

3. 懶漢模式之線程安全

我們看到在使用懶漢模式創建實例時是線程不安全的,這裡我們使用同步鎖的方式,讓懶漢模式也是線程安全的,代碼如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton01</p >
 * <p>Description: 懶漢模式 </p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:41
 * @Version: 1.0
 */
public class Singleton03 {

    private Singleton03() {

    }

    //單例對象
    private static Singleton03 instance = null;

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:43
     * @return: Singleton01
     */
    public static synchronized Singleton03 getInstance() {
        if (instance == null) {
            instance = new Singleton03();
        }
        return instance;
    }
}

這裡我們使用了synchronized關鍵字保證了線程安全,但是在實際開發中不要這樣寫,因為synchronized會帶來比較嚴重的性能開銷。

4. 懶漢模式之雙重同步鎖模式(線程不安全)

使用雙重同步鎖實現單例模式,代碼如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton01</p >
 * <p>Description: 懶漢模式 </p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:41
 * @Version: 1.0
 */
public class Singleton04 {

    private Singleton04() {

    }

    //單例對象
    private static Singleton04 instance = null;

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:43
     * @return: Singleton01
     */
    public static  Singleton04 getInstance() {
        if (instance == null) {
            synchronized (Singleton04.class) {
                if (instance == null) {
                    instance = new Singleton04();
                }
            }
        }
        return instance;
    }
}

上面代碼在synchronized這裡啟用了雙重檢測機制,但是這種寫法在多線程情況下也是線程不安全的,這個原因是JVM和CPU在的指令重排;這裡簡單的介紹一下什麼是電腦指令:

Java實例化一個對象時,主要經歷下麵三步指令:

第一步:分配對象記憶體空間

第二步:初始化對象

第三步:設置instance指向剛分配的記憶體

知道了上面步驟,我們知道一旦在多線程情況下發生了指令重排,就會出現安全問題,例如上面的步驟123變成132

5. 懶漢模式之雙重同步鎖模式(線程安全)

使用第四種方式創建單例也是線程不安全的,要想使第四個例子線程安全,我們需要使用到一個關鍵字:volatile。

代碼如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton01</p >
 * <p>Description: 懶漢模式 </p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:41
 * @Version: 1.0
 */
public class Singleton05 {

    private Singleton05() {

    }

    //單例對象
    private volatile static Singleton05 instance = null;

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:43
     * @return: Singleton01
     */
    public static Singleton05 getInstance() {
        if (instance == null) {
            synchronized (Singleton05.class) {
                if (instance == null) {
                    instance = new Singleton05();
                }
            }
        }
        return instance;
    }
}

volatile可以禁止指令重排,所以上面方式是線程安全的。volatile使用場景主要是:狀態標示量、雙重檢測。

6. 餓漢模式之靜態域實現(錯誤寫法)

餓漢模式第一種實現使用了直接初始化方式,這裡我們可以使用靜態域實現,這也是在平時開發中比較常見的一種方式,但是這裡有一個坑,先埋給大家,代碼如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton02</p >
 * <p>Description: 餓漢模式</p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:53
 * @Version: 1.0
 */
public class Singleton06 {

    private Singleton06() {

    }

    static {
        instance = new Singleton06();
    }
    //單例對象
    private static Singleton06 instance = null;

    /**
     * @description: 靜態的工廠方法
     * @auther: qiwei
     * @date: 2019/8/20 22:55
     * @return: Singleton02
     */
    public static Singleton06 getInstance() {
        return instance;
    }
}

上面靜態域單例模式是有問題的,大家想想有什麼問題,在最後我們再討論,大家也可以debug調試找找問題。

7. 單例模式之枚舉創建

在前面集中創建單例的例子中,我們接觸了懶漢模式、餓漢模式。個人認為不管是懶漢模式還是餓漢模式,都不是最好的方案,這裡我推薦使用枚舉類創建單例,既保證了線程安全的需求,又保證了資源不被浪費。代碼實現如下:

package com.study.concurrency.base_concurrency.example.singleton;

/**
 * <p>Title: Singleton01</p >
 * <p>Description: 枚舉模式 </p >
 * <p>Company: http://www.yinjiedu.com</p >
 * <p>Project: concurrency</p >
 *
 * @author: qiwei
 * @Date: 2019/8/20 22:41
 * @Version: 1.0
 */
public class Singleton07 {

    //私有構造函數
    private Singleton07() {

    }

    //單例對象
    private static Singleton07 instance = null;


    public static Singleton07 getInstance() {
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton {
        INSTANCE;

        private Singleton07 singleton;

        Singleton() {
            singleton = new Singleton07();
        }

        public Singleton07 getInstance() {
            return singleton;
        }
    }
}

上面代碼,在使用枚舉構造方法的時候JVM保證這個方法絕對只調用一次,個人推薦這種創建單例的模式。

解決6:餓漢模式之靜態域實現問題

第六種餓漢模式創建單例的方式,主要是因為代碼位置問題引起的,正確寫法是把下麵兩段代碼交換位置:

private static Singleton06 instance = null;
static {
    instance = new Singleton06();
}

這是在使用靜態域時候特別容易犯的錯誤,希望大家註意。

關於單例模式的分享就到這裡,不足之處希望大家提出來,我會及時修正。

  獲取實時信息,關註公眾號:『編程之藝術』,二維碼:

  

  

  

  

  

  

  


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

-Advertisement-
Play Games
更多相關文章
  • 解決方法: (1)用的是 Ext.getCmp(id).setText('XXXX')可以動態設置label 顯示的文本值,但是文本中有個別數字需要改變顏色顯示,需要加樣式,這種方法會把加樣式的標簽(html)以文本的方式顯示出來,而不會被瀏覽器解析。。。 (2) Ext.getCmp(id).ge ...
  • 上篇文章介紹瞭如何搭建 react cesium 開發環境。在開發環境下,項目一切運行正常。最近把項目打包發佈出來,卻遇見了 cesium 不能正確初始化。打開瀏覽器的調試面板,出現好多 404,資源路徑錯誤。下麵是項目的資源處理過程整理,其中 cesium 需要獨立處理,大家以後要註意。 一、re ...
  • 0.1 + 0.2 = 0.30000000000000004 ??? 浮點數,主要形成原因是,電腦中的所有數據都是以二進位形式存在,具體原因不在這裡說了,剛開始看了別人的文章,寫了好長一段,還是很牛 * 的,但是太長,有些比較簡單的加減有點殺雞用牛刀的感覺, 我用一行代碼解決的方案在最底下。 別 ...
  • 最近看了很多文章,偶然間看到ES6中新增了一個關鍵字 let ,它具有與 var 關鍵字相似的功能。一開始使用它時,發現它讓我對之前一些習以為常的東西產生了懷疑。 下麵先讓我們看看它和 var 之間用法的不同 { var a = 10; let b = 8; } a //10 b // Refere ...
  • 一、延時器(setTimeout) 1.延時器(setTimeout)的工作方式是:當設定一個延時器是5s後進行時,並不代表它5s後就立即執行,只是代表它5s後會被加入隊列,如果5s後,隊列沒有其他東西,那麼延時器的代碼會立即執行,否則會延遲執行。 因此,關於延時器最重要的一點是:指定的時間間隔(如 ...
  • 動手搭建一個基於Webpack的Hello World應用。這次搭建分為兩部分:一部分是前期必要配置,一部分是開發React代碼。 ...
  • jQuery是一個Javascript庫,它支持鏈式操作方式,即對發生在同一個JQuery對象上的一組動作,可以直接接連寫無需要重覆獲取對象。這一特點使得JQuery的代碼無比優雅,而且有強大的選擇器,出色的DOM操作,可靠的事件處理機制,完善的Ajax,關鍵是有出色的瀏覽器相容性,開發項目時可以不 ...
  • 1. ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...