深度解析單例模式

来源:https://www.cnblogs.com/zhuoblog/archive/2023/03/14/17215471.html
-Advertisement-
Play Games

飢漢模式 package com.cz.single; /** * @author 卓亦葦 * @version 1.0 * 2023/3/11 21:31 */ public class Hungry { private byte[] data1 = new byte[1024]; private ...


飢漢模式

package com.cz.single;

/**
 * @author 卓亦葦
 * @version 1.0
 * 2023/3/11 21:31
 */
public class Hungry {

    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];
    private Hungry(){

    }

    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance(){
        return hungry;
    }

    public static void main(String[] args) {
     .   Hungry.getInstance();
    }
}

會浪費記憶體,執行代碼,其4個對象已經被創建,浪費空間。

懶漢式單例

package com.cz.single;

/**
 * @author 卓亦葦
 * @version 1.0
 * 2023/3/11 21:35
 */
public class LazyMan {

    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private volatile static LazyMan lazyMan;

    public static LazyMan getLazyMan(){
        if ((lazyMan==null)){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getLazyMan();
            }).start();
        }
    }
}

懶漢式為使用該對象才創建新對象,但是初始代碼有問題,單線程初始沒有問題,多線程會造成,非單例。

解決辦法,首先加鎖,先判斷對象是否為空,如果為空則將class對象進行上鎖,然後需再判斷,鎖是否為空,如果為空再創建新對象。

同步代碼塊簡單來說就是將一段代碼用一把鎖給鎖起來, 只有獲得了這把鎖的線程才訪問, 並且同一時刻, 只有一個線程能持有這把鎖, 這樣就保證了同一時刻只有一個線程能執行被鎖住的代碼。第二層,是因為使用同步代碼塊才加上的,有的可能過了第一個if,沒到同步代碼塊

為雙層檢測的懶漢式單例,也稱DCL懶漢式

第二個問題

lazyMan = new LazyMan();

代碼為非原子性操作

創建新對象的底層操作分為3步

1.分配記憶體空間
2、執行構造方法,初始化對象
3、把這個對象指向這個空間
但如果不是原子操作,那132的狀況式可能發現的,如果在A還沒完成構造是,線程B進來,則不會執行if語句,發生錯誤

讓lazyMan加上volatile參數

反射會破壞單例模式

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                LazyMan.getLazyMan();
//            }).start();
//        }
        LazyMan lazyMan1 = LazyMan.getLazyMan();
        //獲得空參構造器
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        //反射的setAccessible(true)參數,無視私有構造器
        declaredConstructor.setAccessible(true);
        //通過反射建造對象
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

image-20230314155639972

解決辦法,在無參構造器添加異常

private LazyMan(){
    synchronized (LazyMan.class){
        if (lazyMan!=null){
            throw new RuntimeException("不要試圖反射破壞");
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

image-20230314160149602

但依然存在問題, if (lazyMan!=null)為非原子性操作,依然存在兩個反射對象導致出現非單例的狀況

//可以採用紅綠燈解決,定義一個密鑰
private static boolean key = false;
private LazyMan(){
    synchronized (LazyMan.class){
        if (key==false){
            key=true;
        }else {

                throw new RuntimeException("不要試圖反射破壞");
             
        }
    }
    System.out.println(Thread.currentThread().getName()+"  OK");
}

但是如果仍然用反射破環,假設獲取到了密鑰的情況

//通過反射修改靜態參數
Field key1 = LazyMan.class.getDeclaredField("key");
key1.setAccessible(true);

//獲得空參構造器
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//反射的setAccessible(true)參數,無視私有構造器
declaredConstructor.setAccessible(true);
//通過反射建造對象
LazyMan lazyMan1 = declaredConstructor.newInstance();

//修改對象的密鑰參數
key1.set(lazyMan1,false);

LazyMan lazyMan2 = declaredConstructor.newInstance();

System.out.println(lazyMan1);
System.out.println(lazyMan2);

單例仍然會被破壞

真實有效的方式枚舉

package com.cz.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @author 卓亦葦
 * @version 1.0
 * 2023/3/14 16:26
 */
public enum EnumSingle {

    INSTANCE;

    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //EnumSingle instance2 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

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

image-20230314163414487

至此才得以真正解決


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

-Advertisement-
Play Games
更多相關文章
  • 一、前言 大家好,我是六哥! 又有好長一段時間沒更文了,不是我懶,而是確實在更文上,沒有以前積極了,這裡是該自我檢討的。 其實不是我不積極,而是相對更文學習來說,優先順序不是最高。 對我而言,目前最重要的就是減肥,除了下雨下雪,我都堅持每天5公裡,keep記錄如下: 早在四天前就完成了playwrig ...
  • 前言 🍊緣由 經常看到網上很多優秀的開源項目中,代碼簡潔,模塊分層完美。反觀自己代碼層級混亂,,卻不知如何整理分層。此文手把手教你一步一步創建模塊,左手右手一個慢動作。結合本人實際開發及個人項目中分層思路的分享,大神請勿噴。 ⏲️本文閱讀時長 約25分鐘 🎯主要目標 熟練掌握SpringBoot ...
  • MyBatis 1、簡介 1.1 什麼是Mybatis MyBatis 是一款優秀的持久層框架; 它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或註解來配置和映射原始類型、介面和 J ...
  • 版權聲明:原創不易,本文禁止抄襲、轉載,侵權必究! 一、去吧!皮卡丘! 使用turtle(海龜庫)製作而成,感覺挺好玩的,哈哈@>_<@,效果如下: 由於源碼過長,這裡僅展示部分代碼: from turtle import * import turtle as t from random impor ...
  • 在做項目的時候,安裝MybatisX插件可以讓我們不用寫實體類,加快我們的開發速度,讓我們更專註於業務邏輯的開發,可是最近在做項目的時候,發現MybatisX插件的MybatisX-Generator無法生成實體類,但是其它的文件都可以自動生成。 原因:idea的版本(本人電腦idea版本是2021 ...
  • 引言 大家好,我是蠟筆小曦。 我們在通過程式向某個網頁發起請求時,實際上是模擬瀏覽器進行http(超文本傳輸協議)請求,這就要求我們需要按照固定的格式進行代碼構造。 一般請求數據分為三部分:請求行、請求頭、請求體,如果每次都手動進行這些內容的構造,無疑會花費大量的時間,準確性也難以保證。 現在就給大 ...
  • python語言基礎 1.1 python語法特點 1.1.1 註釋規則 什麼是註釋? 所謂註釋,就是在代碼中添加標註性的文字,進而更好的幫助我們更好的閱讀代碼,註釋又分為單行註釋和多行註釋。 1. 單行註釋 1 單行註釋用: # 來註釋 2 例: 3 print("hello word! ") # ...
  • 一、進位數的表示 十進位——直接表示:10二進位——(首碼):0B1010八進位——(首碼):0O12十六進位——(首碼):0XA 二、進位轉換 (1)十進位轉其他 十進位轉二進位: bin(10)十進位轉八進位: oct(10)十進位轉十六進位:hex(10) 備註:註意以上輸出類型均為字元串,另 ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...