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

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

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);
    }
}

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

更多相關文章
  • 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制定了面向對象編程的五項原則。這五個 ...
一周排行
  • 比如要拆分“呵呵呵90909086676喝喝999”,下麵當type=0返回的是中文字元串“呵呵呵,喝喝”,type=1返回的是數字字元串“90909086676,999”, private string GetStrings(string str,int type=0) { IList<strin ...
  • Swagger一個優秀的Api介面文檔生成工具。Swagger可以可以動態生成Api介面文檔,有效的降低前後端人員關於Api介面的溝通成本,促進項目高效開發。 1、使用NuGet安裝最新的包:Swashbuckle.AspNetCore。 2、編輯項目文件(NetCoreTemplate.Web.c ...
  • 2020 年 7 月 30 日, 由.NET基金會和微軟 將舉辦一個線上和為期一天的活動,包括 微軟 .NET 團隊的演講者以及社區的演講者。本次線上大會 專註.NET框架構建微服務,演講者分享構建和部署雲原生應用程式的最佳實踐、模式、提示和技巧。有關更多信息和隨時瞭解情況:https://focu... ...
  • #abp框架Excel導出——基於vue #1.技術棧 ##1.1 前端採用vue,官方提供 UI套件用的是iview ##1.2 後臺是abp——aspnetboilerplate 即abp v1,https://github.com/aspnetboilerplate/aspnetboilerp ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 作者:碧茂大數據 PS:如有需要Python學習資料的小伙伴可以加下方的群去找免費管理員領取 input()輸入 Python提供了 input() 內置函數從標準輸入讀入一 ...
  • 從12年到20年,python以肉眼可見的趨勢超過了java,成為了當今It界人人皆知的編程語言。 python為什麼這麼火? 網路編程語言搜索指數 適合初學者 Python具有語法簡單、語句清晰的特點,這就讓初學者在學習階段可以把精力集中在編程對象和思維方法上。 大佬都在用 Google,YouT ...
  • 在社會上存在一種普遍的對培訓機構的學生一種歧視的現象,具體表現在,比如:當你去公司面試的時候,一旦你說了你是培訓機構出來的,那麼基本上你就涼了,那麼你瞞著不說,然後又通過了面試成功入職,但是以後一旦在公司被髮現有培訓經歷,可能會面臨被降薪,甚至被辭退,培訓機構出來的學生,在用人單位眼裡就是能力低下的 ...
  • from typing import List# 這道題看了大佬寫的代碼,經過自己的理解寫出來了。# 從最外圍的四周找有沒有為O的,如果有的話就進入深搜函數,然後深搜遍歷# 判斷上下左右的位置是否為Oclass Solution: def solve(self, board: List[List[s ...
  • import requests; import re; import os; # 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, li ...
  • import requests; import re; import os; import parsel; 1.請求網頁 header = { "user-agent":'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537. ...