最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JAVA從頭開始講透、實(shí)現(xiàn)單例模式的探索之路

 更新時(shí)間:2026年05月12日 08:56:36   作者:曄子yy  
java單例模式是一種常見的設(shè)計(jì)模式,在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例的特殊類,下面這篇文章主要介紹了JAVA從頭開始講透、實(shí)現(xiàn)單例模式的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

一、前言

單例模式幾乎是 Java 面試?yán)锏?strong>“常駐嘉賓”。很多人會(huì)背幾種寫法,但一旦面試官繼續(xù)追問“為什么線程不安全”“為什么要加 volatile”“靜態(tài)內(nèi)部類為什么可行”,就容易卡住。

這篇文章不只講“怎么寫”,更想講清楚“為什么這么寫”。我們從最基礎(chǔ)的實(shí)現(xiàn)開始,一步步手撕到線程安全、性能優(yōu)化,以及反射和序列化這些高頻追問。

二、什么是單例模式

單例模式,顧名思義,就是一個(gè)類在整個(gè)系統(tǒng)中只允許存在一個(gè)實(shí)例,并且提供一個(gè)全局訪問入口。

它的核心目標(biāo)只有兩個(gè):

  1. 保證唯一實(shí)例
  2. 提供統(tǒng)一訪問方式

常見場景包括:

  • 配置中心
  • 日志組件
  • 數(shù)據(jù)庫連接管理器
  • 緩存管理器
  • 線程池管理器

聽起來很簡單,但真正難的地方在于:既要保證只有一個(gè)對(duì)象,又要在并發(fā)環(huán)境下安全,還希望性能別太差。

一個(gè)標(biāo)準(zhǔn)的單例,通常要滿足:

  1. 構(gòu)造器私有化,防止外部 new
  2. 類內(nèi)部自己持有唯一實(shí)例
  3. 對(duì)外提供獲取實(shí)例的方法

最基本的結(jié)構(gòu)如下:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

三、實(shí)現(xiàn)單例模式多種版本

1.懶漢式寫法

懶漢式,最容易寫,也最容易出問題,思路是:對(duì)象先不創(chuàng)建,等第一次用到時(shí)再創(chuàng)建。

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

優(yōu)點(diǎn)

  • 延遲加載
  • 寫法直觀

缺點(diǎn)

  • 線程不安全

為什么線程不安全?

假設(shè)線程 A 和線程 B 同時(shí)進(jìn)入 getInstance(),并且都判斷 instance == null 成立,那么它們都會(huì)執(zhí)行 new Singleton(),最終就可能創(chuàng)建出多個(gè)對(duì)象。

所以,這一版只能在單線程環(huán)境下用,面試?yán)锶绻粚懙竭@里,基本一定會(huì)被追問。

也可以在getInstance()上加鎖防止線程安全問題

2.餓漢式寫法

餓漢式,簡單粗暴,天然線程安全,思路和懶漢式相反:類加載時(shí)就直接把實(shí)例創(chuàng)建好。

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

    private Singleton() {
    }

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

優(yōu)點(diǎn)

  • 實(shí)現(xiàn)簡單
  • 天然線程安全
  • 調(diào)用性能好

為什么線程安全?

因?yàn)轭惣虞d過程本身就是線程安全的,JVM 會(huì)保證類只加載一次,所以靜態(tài)實(shí)例也只會(huì)初始化一次。

缺點(diǎn)

  • 沒有延遲加載
  • 如果這個(gè)對(duì)象一直沒被用到,就會(huì)造成一定資源浪費(fèi)

適用場景

如果這個(gè)單例對(duì)象本身很輕量,或者項(xiàng)目啟動(dòng)后大概率一定會(huì)用到,那餓漢式完全沒問題。

3.雙重檢查鎖 DCL

為了兼顧線程安全和性能,很多人會(huì)想到:既然每次都加鎖太重,那能不能只在第一次創(chuàng)建時(shí)加鎖?

這就是雙重檢查鎖。

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

為什么要判空兩次

第一次 if (instance == null):

  • 避免每次都進(jìn)入同步塊
  • 提高性能

第二次 if (instance == null):

  • 防止多個(gè)線程進(jìn)入第一層判斷后,重復(fù)創(chuàng)建對(duì)象

為什么 volatile 不能少

這部分是面試最愛問的點(diǎn)。

new Singleton() 這行代碼看起來像一個(gè)原子操作,但實(shí)際上底層大致會(huì)經(jīng)歷三步:

  1. 分配內(nèi)存
  2. 初始化對(duì)象
  3. 將引用賦值給 instance

問題在于,JVM 可能發(fā)生指令重排,變成:

  1. 分配內(nèi)存
  2. 將引用賦值給 instance
  3. 初始化對(duì)象

如果線程 A 執(zhí)行到第 2 步,此時(shí)線程 B 進(jìn)來發(fā)現(xiàn) instance != null,就直接返回了一個(gè)“還沒初始化完成”的對(duì)象,這就會(huì)出問題。

volatile 的作用就是:

  • 禁止指令重排
  • 保證可見性

所以,DCL 必須搭配 volatile 使用,否則就是不完整寫法。

4.靜態(tài)內(nèi)部類

很多時(shí)候,工程里更推薦靜態(tài)內(nèi)部類寫法。

public class Singleton {
    private Singleton() {
    }

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

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

為什么它線程安全

因?yàn)殪o態(tài)內(nèi)部類 Holder 不會(huì)在外部類加載時(shí)立即加載,只有第一次調(diào)用 getInstance() 時(shí),才會(huì)觸發(fā) Holder 的加載和初始化。

而類加載過程又是線程安全的,因此它天然保證了:

  • 延遲加載
  • 線程安全
  • 不需要顯式加鎖

優(yōu)點(diǎn)

  • 寫法簡潔
  • 性能好
  • 延遲加載
  • 線程安全

很多場景下,這一版是比 DCL 更推薦的選擇。

四、單例模式會(huì)被怎么破壞

很多人以為把構(gòu)造器私有化就萬無一失了,其實(shí)不夠。

1. 反射破壞

即使構(gòu)造器是 private,也可以通過反射強(qiáng)行訪問。

Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton s1 = constructor.newInstance();
Singleton s2 = constructor.newInstance();

System.out.println(s1 == s2); // false

如何防御

可以在構(gòu)造器里增加判斷:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new RuntimeException("Singleton already exists");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 序列化破壞

如果單例類實(shí)現(xiàn)了 Serializable,序列化再反序列化后,可能得到一個(gè)新對(duì)象。

Singleton s1 = Singleton.getInstance();
// 序列化 s1
// 反序列化得到 s2
System.out.println(s1 == s2); // 可能是 false

如何防御

實(shí)現(xiàn) readResolve():

public class Singleton implements Serializable {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }

    private Object readResolve() {
        return INSTANCE;
    }
}

這樣反序列化時(shí),返回的仍然是原來的單例對(duì)象。

五、單例模式總結(jié)

單例模式表面上只是“讓一個(gè)類只能創(chuàng)建一個(gè)對(duì)象”,但真正的難點(diǎn)在于并發(fā)安全和邊界問題。

我們可以把它理解成三個(gè)層次:

  1. 會(huì)寫單例
  2. 寫對(duì)線程安全的單例
  3. 理解為什么這樣寫,以及它還會(huì)被什么方式破壞

如果只讓我給一個(gè)工程里比較推薦的版本,我會(huì)優(yōu)先選靜態(tài)內(nèi)部類:

public class Singleton {
    private Singleton() {
    }

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

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

單例模式真正要掌握的,不是背下哪幾段代碼,而是明白:

  • 為什么懶漢式會(huì)出并發(fā)問題
  • 為什么 DCL 要配 volatile
  • 為什么類加載機(jī)制能保證線程安全
  • 為什么反射和序列化可能破壞單例

到此這篇關(guān)于JAVA實(shí)現(xiàn)單例模式的文章就介紹到這了,更多相關(guān)JAVA單例模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文詳解Java攔截器與過濾器的使用

    一文詳解Java攔截器與過濾器的使用

    這篇文章主要為大家詳細(xì)介紹了Java中攔截器與過濾器的使用與區(qū)別,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)有一定參考價(jià)值,需要的可以參考一下
    2022-04-04
  • Java中序列化與反序列化的定義及代碼示例

    Java中序列化與反序列化的定義及代碼示例

    序列化是將對(duì)象狀態(tài)轉(zhuǎn)換為可存儲(chǔ)或傳輸格式的過程,反序列化則是將存儲(chǔ)或傳輸格式恢復(fù)為原始對(duì)象的逆過程,這篇文章主要介紹了Java中序列化與反序列化的相關(guān)資料,需要的朋友可以參考下
    2024-10-10
  • SpringBoot+Idea熱部署實(shí)現(xiàn)流程解析

    SpringBoot+Idea熱部署實(shí)現(xiàn)流程解析

    這篇文章主要介紹了SpringBoot+Idea熱部署實(shí)現(xiàn)流程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • java中的xxxable和xxxator使用及說明

    java中的xxxable和xxxator使用及說明

    這篇文章主要介紹了java中的xxxable和xxxator使用及說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java 入門圖形用戶界面設(shè)計(jì)之單選按鈕

    Java 入門圖形用戶界面設(shè)計(jì)之單選按鈕

    圖形界面(簡稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對(duì)于用戶來說在視覺上更易于接受,本篇精講Java語言中關(guān)于圖形用戶界面的單選按鈕
    2022-02-02
  • 深入淺析Java 虛擬線程

    深入淺析Java 虛擬線程

    Java21引入了虛擬線程,這是一種輕量級(jí)線程,適用于IO密集型的應(yīng)用,可以極大提高應(yīng)用的性能和吞吐量,虛擬線程是由傳統(tǒng)線程執(zhí)行的,由JVM控制上下文切換,創(chuàng)建和銷毀的開銷小,適用于高并發(fā)場景,本文介紹Java 虛擬線程的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧
    2025-02-02
  • JPA如何使用findBy方法自定義查詢

    JPA如何使用findBy方法自定義查詢

    這篇文章主要介紹了JPA如何使用findBy方法自定義查詢,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • java jar 命令檢查隱藏啟動(dòng)日志的方法

    java jar 命令檢查隱藏啟動(dòng)日志的方法

    這段文章主要介紹了如何解決Spring Boot啟動(dòng)失敗的問題,通過直接運(yùn)行Jar、開啟Debug模式、指定dev環(huán)境以及輸出日志文件等方法,幫助開發(fā)者快速定位并解決啟動(dòng)過程中的異常和錯(cuò)誤
    2026-06-06
  • IDEA與JDK、Maven安裝配置完整步驟解析

    IDEA與JDK、Maven安裝配置完整步驟解析

    這篇文章主要介紹了如何安裝和配置IDE(IntelliJ?IDEA),包括IDE的安裝步驟、JDK的下載與配置、Maven的安裝與配置,以及如何在IDE中使用Maven進(jìn)行Java開發(fā),需要的朋友可以參考下
    2025-03-03
  • Java獲取客戶端真實(shí)IP地址經(jīng)典寫法詳解

    Java獲取客戶端真實(shí)IP地址經(jīng)典寫法詳解

    在Web開發(fā)中,獲取客戶端的真實(shí)IP地址是一個(gè)常見需求,本文將分別給出經(jīng)典寫法(基于工具類/條件判斷)和Lambda寫法(基于函數(shù)式接口?+?流式處理)的實(shí)現(xiàn),并進(jìn)行對(duì)比
    2026-04-04

最新評(píng)論

安福县| 天全县| 五寨县| 巧家县| 外汇| 华蓥市| 临高县| 普格县| 拉萨市| 崇信县| 沅江市| 怀化市| 德格县| 乐昌市| 尤溪县| 如皋市| 成安县| 林芝县| 平山县| 依兰县| 汕尾市| 昭苏县| 谷城县| 讷河市| 公主岭市| 扶绥县| 阳西县| 通海县| 桂阳县| 夏津县| 高要市| 吉木萨尔县| 长治县| 信宜市| 崇文区| 葵青区| 通州区| 庆元县| 盐亭县| 铜梁县| 鄂尔多斯市|