設計模式之(4)——單例模式

来源:https://www.cnblogs.com/wha6239/archive/2022/08/29/16635112.html
-Advertisement-
Play Games

YSLaunchar-a1.0 模型 基本介紹 本文不考慮所有具體的實現方法,之後會有更完整第二版發出 該程式計劃使用 julia 語言編寫,目前版本(1.7)並不包含類(class),取而代之,我會使用 julia 提供的兩種結構體完成。 考慮了很久,我將會把所有版本,玩家列表使用字典的形式。 主 ...


  定義:單例模式屬於創建型模式,該類負責創建自己的對象實例,並且確保只有單個對象被創建,同時該類提供了一種全局訪問其唯一實例對象的方式;這個定義中有三個要點:1、單例類只能有一個實例;2、單例類必須自己創建自己的唯一實例;3、單例類必須可以給其他所有對象提供這一唯一實例;

  意圖:保證一個類僅有一個實例,並提供一個訪問它的全局節點;

  主要解決:一個全局使用的對象的頻繁地創建和銷毀;

  何時使用:想控制類的數目,節省系統資源的時候;

  如何解決:判斷系統是否已經有這個實例,有則直接返回,沒有則創建返回;

  關鍵代碼:構造函數私有;

  應用實例:

  1、一個班級只有一個班主任;

  2、Windows是多進程多線程的操作系統,在操作一個文件的時候,就不可避免地出現多個線程或者多個進程同時操作一個文件的現象,所有的文件處理必須通過一個唯一的實例來進行處理;

  優點:

  1、記憶體中只有相關對象的一個實例,減少了記憶體開銷,尤其是頻繁地創建和銷毀的實例;

  2、避免了對資源的多重占用;

  缺點:

  1、沒有介面,不能繼承,與單一職責衝突,一個類應該只關心內部邏輯,而不應該關係外部怎麼來實例化它;

  使用場景:

  1、要求生產唯一序列號;

  2、WEB中的計數器,不用每次刷新都在資料庫中更新一次,用單例先緩存起來;

  3、創建一個對象需要消耗過多的系統資源的時候,比如I/O和資料庫連接等等;

  單例模式的幾種常見寫法:

package cn.com.pep.model.singleton.single2;
/**
 * 
 * @Title: Singleton  
 * @Description: 線程安全的懶漢式 
 * @author wwh 
 * @date 2022-8-24 15:07:07
 */
public class Singleton {
    
    /**
     * 是否是懶載入:是
     * 是否線程安全:是
     * 實現難度:容易
     * 描述:這種方式具備很好的lazy loading,能夠在多線程中很好的工作,但是效率比較低,99%情況下不需要同步;
     * 優點:第一次調用才初始化,避免了記憶體的浪費;
     * 缺點:必須加鎖synchronized才能保證單例,在靜態方法上加的是類鎖會影響效率;
     */
    
    private static Singleton instance;
    
    private Singleton() {
        // TODO Auto-generated constructor stub
    }
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        
        return instance;
    }

}

 

package cn.com.pep.model.singleton.single3;

/**
 * 
 * @Title: Singleton
 * @Description:餓漢式單例
 * @author wwh
 * @date 2022-8-24 15:45:01
 */
public class Singleton {
    /**
     * 是否是懶載入:否
     * 是否線程安全:是
     * 實現難度:容易
     * 描述:比較常用,但是容易產生垃圾對象;
     * 優點:沒有加鎖,執行效率會提高;
     * 缺點:類載入時候就會初始化,浪費記憶體;
     * 它基於classloader機制避免了多線程同步的問題,不過,instance在類裝載的時候就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance,
     * 但也不確定有其他方式導致類載入,這時候就沒有達到懶載入的效果;
     */
    
    private static Singleton instance = new Singleton();

    private Singleton() {
        // TODO Auto-generated constructor stub
    }

    public static Singleton getInstance() {
        return instance;
    }
package cn.com.pep.model.singleton.single4;
/**
 * 
 * @Title: Singleton
 * @Description:雙重校驗鎖
 * @author wwh
 * @date 2022-8-24 16:02:19
 */
public class Singleton {
    
    /**
     * 是否是懶載入:是
     * 是否多線程安全:是
     * 實現難度:較複雜
     * 描述:這種方式採用鎖雙鎖機制,安全且在多線程情況下能保持高性能;
     */
    // volatile關鍵字在這使用時,是利用其禁止虛擬機的指令重排特點,防止返回一個未被完整初始化的對象;
    private volatile static Singleton instance;

    private Singleton() {
        // TODO Auto-generated constructor stub
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
          //假如當前有兩個線程同時執行獲取實例的方法,cpu執行A線程執行到這個地方說明instance==null這個條件成立,此時發生了線程切換,當前線程釋放了類鎖,此時
          //cpu執行B線程,執行完之後instance!=null,然後又切換回A線程執行,如果不instance== null做判斷會再將instace初始化一次,可能會引起其他未知的錯誤。
if (instance == null) { instance = new Singleton(); } } } return instance; } }
package cn.com.pep.model.singleton.single5;

/**
 * @Title: Singleton
 * @Description: 靜態內部類
 * @author wwh
 * @date 2022-8-24 16:12:51
 */
public class Singleton {
    
    /**
     * 是否是懶載入:是
     * 是否是多線程安全:是
     * 實現難度:一般
     * 描述:這種方式能達到和雙檢鎖方式一樣的功效,實現更簡單;
     * 這種方式同樣利用了classloader機制來保證實例初始化的時候只有一個線程,它跟餓漢式不同的是:
     * 第三種方式,只要Sington類被載入了,那麼instance實例就會被初始化,沒有達到懶載入的效果,
     * 而這種方式即使Singleton類被載入了,但是SingletonHolder類沒有被主動使用,只有通過顯示調用
     * getInstance()方法時候,才會顯示載入SingletonHolder類,從而實例化instance對象;
     */

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

    private Singleton() {
        // TODO Auto-generated constructor stub
    }

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

}

    以上的四種寫法大部分精力都在致力於保證多線程併發下的線程安全;因為構造方法都是私有的,也可以避免由反射獲取對象實例(在未開啟忽略許可權檢查的情況下);但是還沒有解決由於序列化和反序列化導致獲取的不是同一個實例的問題,那麼怎麼解決呢?

  答案是:在單例類中增加一個readResolve()方法,返回這個唯一的實例,就可以解決這個問題啦,這是因為反序列化的readObject()底層會做個判斷,假如當前反序列化的目標對象有ReadResolve()方法,那麼會調用目標類的這個方法返回一個實例對象。

  最後一種單例寫法,通過枚舉來實現單例,這種比較推薦,這種寫法天然就是線程安全的,所以我們就不需要花費大量的精力來保證線程安全,同時既可以防止反序列化生成不同實例,又可以防止反射生成不同實例:

package cn.com.pep.model.singleton.single6;

import java.io.Serializable;

/**
 * 
 * @Title: Singleton
 * @Description:
 * @author wwh
 * @date 2022-8-24 16:49:19
 */
public enum Singleton implements Serializable{

    SPRING{

        @Override
        public void say(String message) {
            System.err.println(message);
        }
        
    };
    
    public abstract void say(String message);
}

 

  為什麼既可以避免反射生成不同實例,又可以避免反序列化生成不同實例呢?下麵我們一一道來:

  1、至於為什麼通過反射不能生成實例對象呢?請看下這個枚舉類反編譯之後的代碼,同時還包含一個匿名內部類:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
package cn.com.pep.model.singleton.single6;

import java.io.PrintStream;
import java.io.Serializable;

public abstract class Singleton extends Enum implements Serializable {

    public static final Singleton SPRING;
    private static final Singleton ENUM$VALUES[];

    private Singleton(String s, int i) {
        super(s, i);
    }

    public abstract void say(String s);

    public static Singleton[] values() {
        Singleton asingleton[];
        int i;
        Singleton asingleton1[];
        System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
        return asingleton1;
    }

    public static Singleton valueOf(String s) {
        return (Singleton) Enum.valueOf(cn / com / pep / model / singleton / single6 / Singleton, s);
    }

    Singleton(String s, int i, Singleton singleton) {
        this(s, i);
    }

    static {
        SPRING = new Singleton("SPRING", 0) {

            public void say(String message) {
                System.err.println(message);
            }

        };
        ENUM$VALUES = (new Singleton[]{SPRING});
    }
}

/*    */ // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
/*    */ package cn.com.pep.model.singleton.single6;
/*    */ 
/*    */ import java.io.PrintStream;
/*    */ 
/*    */ 
/*    */ 
/*    */ 
/*    */ class Singleton$1 extends Singleton
/*    */ {
/*    */ 
/*    */     Singleton$1(String s, int i)
/*    */     {
/* 14 */         super(s, i, null);
/*    */     }
/*    */     public void say(String message)
/*    */     {
/* 18 */         System.err.println(message);
/*    */     }
/*    */ }

  仔細分析這個枚舉反編譯之後的代碼,枚舉類的enum其實是個關鍵字,普通的枚舉類其實都是一個繼承了Enum的普通的java子類,所有的枚舉類都具有這個公共的父類,列子中的枚舉類也包含了兩個靜態屬性SPRING和ENUM$VALUES,這兩個靜態屬性的初始化是在靜態塊中進行的,類載入執行靜態塊中的代碼時候會初始化給這兩個靜態屬性賦值,而類載入的過程會調用ClassLoader類的loadClass()方法,這個方法的方法體是用synchronized修飾的,必定是線程安全的,所以我們說這種寫法是線程安全的原因在這,並且這兩個靜態屬性還是final修飾的,一旦初始化完成則不允許修改,初始化完成之後我們的SPRING = new Singleton("SPRING", 0),又因為我們這個枚舉類中包含了抽象方法,根據java規範,抽象方法只能存在於介面或者抽象方法中,所以這個子類反編譯之後必然是用abstract修飾的抽象類,眾所周知抽象類是不能被實例化的,並且這兩個類中沒有定義無參構造方法,所以不能被反射實例化了。

  2、為什麼能防止反序列化生成多個對象呢(當然我們說序列化的前提是類都實現了序列化介面)?

  首先要從枚舉類的序列化說起,枚舉類在序列化的時候其實只是將Singleton(key,value)中的key進行了序列化,而反序列化的時候也是通過這個key去對應的map中獲取對應的Singleton(key,value)實例的,大致就是這麼個邏輯,有興趣可以翻翻源碼看看。  

本文來自博客園,作者:一隻烤鴨朝北走,僅用於技術學習,所有資源都來源於網路,部分是轉發,部分是個人總結。歡迎共同學習和轉載,轉載請在醒目位置標明原文。如有侵權,請留言告知,及時撤除。轉載請註明原文鏈接:https://www.cnblogs.com/wha6239/p/16635112.html


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

-Advertisement-
Play Games
更多相關文章
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="wi ...
  • 1 鏈接地址 1.1 資料地址與鏈接 視頻鏈接(P77~P96): 黑馬程式員Node.js全套入門教程,nodejs最新教程含es6模塊化+npm+expre... 資料鏈接: 鏈接:https://pan.baidu.com/s/1SLBluoHqhtr5r6h3Xb1Dsw 提取碼:60kb ...
  • JavaScript中的數組 數組的概念 ==數組==是指一組數據的集合,其中每個數據被稱作元素,在數組中可以存放任意類型的元素。數組是一種將一組數據存儲在單個變數名下的優雅方式。 創建數組 1.利用new創建數組 var 數組名 = new Array() ; var arr = new Arra ...
  • 1 以下代碼執行後,控制臺中的輸出內容為? for (let i = 0; i < 3; i++) { setTimeout(() => { console.log(i); }); } for (var j = 0; j < 3; j++) { setTimeout(() => { console. ...
  • 實現了自動生成和插槽兩個方式,主要把 el-table 和 el-pagination 封裝在一起 效果圖: 使用組件,啟用自動生成 :auto="true" 自動生成-編輯 (包括請求已經實現了)新增和刪除也是一樣 ps:如有額外的按鈕可以用插槽實現 查詢的時候,只需要多返回下麵數據,就可以自動生 ...
  • 問題: this.$refs[formName].validate((valid) =>{} 無效的問題,當驗證通過的時候點確定按鈕沒有報錯,也沒有任何反應。 背景: ruoyi前後端分離新項目,前端vue。 解決: 參考: this.$refs[formName].validate((valid) ...
  • Vue + Elementui 實現登錄頁 手機驗證碼、倒計時等功能 點擊打開視頻講解 更加詳細 <template> <div id="app"> <div class="left">用代碼改變世界</div> <el-form class="content" ref="refForm" :rul ...
  • 若依前後端分離項目中,提交含有qutil富文本控制項數據的表單,後端接收到的富文本數據,缺少了部分html標簽。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...