java設計模式1——單例模式

来源:https://www.cnblogs.com/xgp123/archive/2020/02/06/12262160.html
-Advertisement-
Play Games

1、單例模式介紹 2、餓漢式 3、懶漢式 4、DCL_懶漢式 5、靜態內部類實現 6、利用枚舉來實現 ...


java設計模式1——單例模式

1、單例模式介紹

1.1、核心作用:保證一個類只有一個實例,並且提供一個訪問該實例的全局訪問點

1.2、常見場景

1.3、單例模式的優點

1.4、常見的五種單例模式實現方式

2、餓漢式

2.1、第一步:私有化構造器。(防止外部直接new對象)

//保證類只有一個實例,私有其構造器
private SingletonDemo01() {

}

2.2、第二步:創建自身對象。

//創建自身對象
private static SingletonDemo01 instance = new SingletonDemo01();

2.3、第三步:提夠對外全局公開的方法

//全局公開的方法
public static SingletonDemo01 getInstance() {
    return instance;
}

2.4、測試是否為單例

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

輸出的結果為:

356573597
356573597
true

2.5、弊端分析:

餓漢式一上來就會對對象進行創建,不管後續有沒有用到,如果對於較大記憶體的對象而後續也都沒有用到,則會造成較大的記憶體空間的浪費。

2.6、本類全部代碼

package com.xgp.company.第一種_單例模式.餓漢式;

/**
 *
 * 核心:保證一個類只有一個實例,並且提供一個範圍該實例的全局訪問點
 */
public class SingletonDemo01 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo01() {

    }

    //創建自身對象
    private static SingletonDemo01 instance = new SingletonDemo01();

    //全局公開的方法
    public static SingletonDemo01 getInstance() {
        return instance;
    }

}

class SingletonDemo01Test {
    public static void main(String[] args) {
        SingletonDemo01 instance = SingletonDemo01.getInstance();
        SingletonDemo01 instance2 = SingletonDemo01.getInstance();

        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }
}

3、懶漢式

目的:解決餓漢式可能存在的記憶體空間浪費的問題進行該進,不一上來就創建對象,而是在使用時再來創建對象。

3.1、懶漢式的代碼如下:

public class SingletonDemo02 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo02() {

    }
    //創建自身對象,當時不用立即載入
    private static SingletonDemo02 instance;

    //全局公開的方法  synchronized作用:加鎖  多線程進來時會不安全,效率較低
    public static synchronized SingletonDemo02 getInstance() {
        if(instance == null) {
            instance = new SingletonDemo02();
        }
        return instance;
    }

}

3.2、分析:代碼中為什要使用synchronized關鍵字來進行上鎖

考率一下多線程的情況下,如果沒有上鎖,兩個線程A、B一前以後的很緊密的執行該方法,而此時A完成了初始化操作,但是還沒有進行返回,B此時進入判斷語句中,此時也為null,這樣也會進行初始化操作,於是乎,就得到了兩個對象了,違反了單例模式設計得原則。

3.3、弊端分析:

該方法使用了synchronized對一個返回得方法進行了上鎖,該方法得執行效率會較慢。

4、DCL_懶漢式

目的:DCL_懶漢式又稱為雙重檢測懶漢式,為了改進懶漢式效率不高的問題

4.1、該類的1版本的代碼如下:

public class SingletonDemo03 {

    //保證類只有一個實例,私有其構造器
    private SingletonDemo03() {
    }
    //創建自身對象,當時不用立即載入 volatile作用:盡大可能的解決極端情況的問題
    private volatile static SingletonDemo03 instance;

    //全局公開的方法  synchronized作用:加鎖  多線程進來時會不安全,效率較低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次進來時加鎖,後面進來時就不加鎖了,提高了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.2、分析1版本代碼:

同樣考率多線程的情況下,A、B兩線程相繼的進入方法中,A率先獲得初始化權力,進行上鎖,進行對對象的創建,並且因為有volatile關鍵字,能夠快速的將對象更新給B。如果B未進入判斷語句中,則此時B中有該類對象了,直接返回了。如果B進入了判斷語句中,但是A已經上鎖了,也無法進入了,只有返回了。

4.3、1版本的弊端

1、再考慮多線程的極端情況,如果該類比較龐大,創建對象需要花費很長時間,B已經進入函數中了,而A創建對象的時間會比B走完該函數的時間長,則此時該函數將會返回B,而B=NULL。

2、該模式無法防止反射

4.4、版本2代碼:

public class SingletonDemo03 {

    //破壞兩次都用反射創建對象
    private static boolean flag = false;

    //保證類只有一個實例,私有其構造器
    private SingletonDemo03() {
        //防治被反射
        synchronized (SingletonDemo03.class) {
            if(flag == false) {
                flag = true;
            }else {
                throw new RuntimeException("不要試圖用反射破壞單例");
            }
        }

    }
    //創建自身對象,當時不用立即載入 volatile作用:盡大可能的解決極端情況的問題
    private volatile static SingletonDemo03 instance;

    //全局公開的方法  synchronized作用:加鎖  多線程進來時會不安全,效率較低
    public static SingletonDemo03 getInstance() {
        if(instance == null) {
            //定一次進來時加鎖,後面進來時就不加鎖了,提高了效率
            synchronized (SingletonDemo03.class) {
                if(instance == null) {
                    instance = new SingletonDemo03();
                }
            }
        }
        return instance;
    }

}

4.5、弊端分析

該版本同樣未解決上面的問題,只是加大了反射獲取對象的難度,反射破壞單例的代碼如下:

class SingletonDemo03Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        /*
        SingletonDemo03 instance1 = SingletonDemo03.getInstance();
        SingletonDemo03 instance2 = SingletonDemo03.getInstance();

        System.out.println(instance1 == instance2);
*/
        Class<SingletonDemo03> clazz = SingletonDemo03.class;

        //反射破壞單例
        Constructor<SingletonDemo03> declaredConstructor = clazz.getDeclaredConstructor(null);

        declaredConstructor.setAccessible(true);

        SingletonDemo03 instance1 = declaredConstructor.newInstance();

        //破壞flag
        Field flag = clazz.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(clazz,false);
        System.out.println(flag.get(clazz));


        SingletonDemo03 instance2 = declaredConstructor.newInstance();

        System.out.println(instance1 == instance2);

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

運行結果:

false
false
21685669
2133927002

5、靜態內部類實現

該方式能夠不適用synchronized提高效率,並且能夠保證在多線程的情況下依舊是單例,代碼如下:

public class SingletonDemo04 {
    private SingletonDemo04() {

        //防治被反射
        synchronized (SingletonDemo04.class) {
            if(InnerClass.instance != null) {
                throw new RuntimeException("不要試圖用反射破壞單例");
            }
        }
    }

    private static class InnerClass {
        private static final SingletonDemo04 instance = new SingletonDemo04();
    }

    public static SingletonDemo04 getInstance() {
        return InnerClass.instance;
    }
}

6、利用枚舉來實現

java中最為推薦的是使用枚舉類來創建單例對象,因為枚舉類有這純天然的優勢,無法被反射。點擊進反射創建對象的newInstance()方法的源碼中可以發現:

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

此外,枚舉類也本身就是單例的,所以使用枚舉類來創建單例對象最為適合,而如今大多數的框架的單例也都是通過這樣的方法進行創建的。代碼如下:

/**
 * 反射不能破壞枚舉類型,枚舉類純天然的單例,最簡單
 */
public enum SingletonDemo05 {
    INSTANCE;

    public SingletonDemo05 getInstance() {
        return INSTANCE;
    }

    public String hello() {
        return "Hello World!";
    }
}

class SingletonDemo05Test {
    public static void main(String[] args) {
        SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
        SingletonDemo05 instance2 = SingletonDemo05.INSTANCE.getInstance();

        System.out.println(instance1 == instance2);

        String hello = SingletonDemo05.INSTANCE.hello();
        System.out.println(hello);
    }
}

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

-Advertisement-
Play Games
更多相關文章
  • SPA單頁面應用容器 開源地址: https://github.com/yuzd/Spa 功能介紹 前端應用開發完後打包後自助上傳部署發佈 配合服務端腳本(javascript)實現服務端業務邏輯編寫渲染SSR功能 可以快速回滾到上一個版本 可以設置環境變數供SSR功能使用 服務端腳本提供執行日誌 ...
  • 最近自學到了JS運動部分,自己整理了一些js模板,望採納。 1.支持鏈式運動的模板: 先解釋一下函數中的幾個參數含義: 1)obj: 要操作的對象 2)target: 屬性要到達的目標值 3)attr: 要操作的屬性值 4)callback: 回調函數 註:調用函數時,回調函數可不寫 2.支持完美運 ...
  • 在vue、react等框架大量應用之前,我們需要使用jQuery或者原生js來操作dom寫代碼,在用原生js進行事件綁定時,我們可以應用DOM2級綁定事件的方法,即:元素.addEventListener(),因為相容性,還有: 元素.attachEvent()。所以我們需要封裝成一個方法: fun ...
  • 原文:https://zhuanlan.zhihu.com/p/23987456?refer=study-fe 大部分講 new 的文章會從面向對象的思路講起,但是我始終認為,在解釋一個事物的時候,不應該引入另一個更複雜的事物。 今天我從「省代碼」的角度來講 new。 想象我們在製作一個策略類戰爭游 ...
  • 1.HTML基礎標簽圖片常見代碼形式<img src="圖片路徑地址" alt="屬性名" title="占位符">常見的圖片格式為以下三種:.jpg(圖片有損壓縮,影響畫質)、.png(圖片無損壓縮、容積大、具有透明通道)、.gif(動圖)。圖片路徑地址分為本地圖片和網路圖片,本地圖片中分為絕對路 ...
  • 1、工廠模式介紹: 2、簡單工廠模式 3、工廠方法模式 4、小結 5、抽象工廠模式 ...
  • 需求 地區數據往往是存在強上下級關係的一種數據結構,在電商系統中是比較常應用到的,比如北京的下級地區只有海澱區、通州區……,而不會是太原市,而且在開發人員傳遞地區值的時候往往要傳遞很多的值,比如省、市、區、鎮、省Id,市id、區id、鎮id,這樣影響了代碼的美觀性及校驗強上下級關係代碼的複雜性。基於 ...
  • 面向對象編程(OOP)給軟體開發領域帶來了新的設計思想。很多開發人員在進行面向對象編程過程中,往往會在一個類中將具有相同目的/功能的代碼放在一起,力求以最快的方式解決當下的問題。但是,這種編程方式會導致程式代碼混亂和難以維護。因此,Robert C. Martin制定了面向對象編程的五項原則。這五個 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...