主頁 >  其他 > 單例模式雙重檢查鎖定與延遲初始化你不得不知道的底層原理

單例模式雙重檢查鎖定與延遲初始化你不得不知道的底層原理

2021-06-15 06:45:17 其他

簡介

在Java多執行緒中,有時候可能需要采用延遲初始化來降低初始化類和創建物件的開銷,雙重檢查鎖(餓漢式單例中經常用)是常見的延遲初始化方案,但它是一個錯誤的用法,本文將分析雙重檢查鎖定的錯誤根源,以及兩張執行緒安全的延遲初始化方案,
?

1、雙重檢查鎖定的由來

在Java程式中,有時候可能需要推遲一些高開銷的物件初始化操作,并且只有在使用這些物件時才進行初始化,此時,程式員可能會采用延遲初始化,但要爭取實作執行緒安全的延遲化需要一些技巧,以此來避免不必要的問題,
非執行緒安全延遲初始化代碼示例:

package com.lizba.p1;

/**
 * <p>
 *      實體物件
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 22:42
 */
public class Instance {

    public Instance() {
        System.out.println("init...");
    }

}
package com.lizba.p1;

/**
 * <p>
 *      延遲初始化
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 22:40
 */
public class UnsafeLazyInitialization {

    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {             // 1、執行緒A執行
            instance = new Instance();      // 2、執行緒B執行
        }
        return instance;
    }

}

在UnsafeLazyInitialization類中,假設執行緒A執行1的同時執行緒B執行2,此時執行緒A可能會看到Instance物件未完成初始化(后續會講問題根源),
?

同步處理解決方法:

package com.lizba.p1;

/**
 * <p>
 *
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 22:46
 */
public class SafeLazyInitialization {

    private static Instance instance;

    public synchronized static Instance getInstance() {
        if (instance == null) {             // 執行緒A執行
            instance = new Instance();      // 執行緒B執行
        }
        return instance;
    }
    
}

給getInstance()方法做了同步處理,synchronized會帶來性能開銷,在getInstance()呼叫不頻繁的情況下,這種解決方案是可以接收的,但是如果getInstance()被頻繁呼叫,程式的整體性能將會下降,(尤其是在早期JVM中,沒有鎖升級策略的時候),
?

雙重檢查鎖解決方法:

package com.lizba.p1;

/**
 * <p>
 *      雙重檢查鎖
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 22:51
 */
public class DoubleCheckedLocking {

    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {                                 // 第一次檢查
            synchronized (DoubleCheckedLocking.class) {         // 加鎖
                if (instance == null) {                         // 第二次檢查
                    instance = new Instance();                  // 仍然存在問題的代碼
                }
            }
        }
        return instance;
    }

}

如上代碼,如果第一次檢查instance不為null,那么久不需要執行加鎖和初始化作業,可以極大的減少synchronized帶來的性能開銷,但是雙重檢查鎖也存在一個問題,就是判斷instance == null這行代碼可能會在Instance未正確初始化的時候成立,這個問題產生的原因是指令重拍,下面會詳細講述,也可以看我往期的文章哈!因此這是一個錯誤的不完美的解決方案,
?

2、問題的根源

2.1 分析 instance = new Instance();

instance = new Instance(); 這行代碼在可以理解為三行偽代碼(JVM中的指令):

  1. memory = allocate(); // 分配物件的記憶體空間
  2. ctorInstance(memory); // 初始化物件
  3. instance = memory; // 設定instance指向剛分配的記憶體地址

?
上述代碼2和3可能會被重排序(部分JIT編譯器真實存在),重排序后如下所示:

  1. memory = allocate(); // 分配物件的記憶體空間
  2. instance = memory; // 設定instance指向剛分配的記憶體地址 (未初始化完成)
  3. ctorInstance(memory); // 初始化物件

由于上述重排序,遵守Java程式執行時必須遵守的intra-thread semantics,重排序并未改變在單執行緒中程式執行結果,且如果該重排序能帶來性能優化則是被Java語言規范《The Java Language Specification》允許的,
?

2.2 分析什么是intra-thread semantics

單執行緒內instance = new Instance(); 執行時序圖:
在這里插入圖片描述

執行緒執行時序圖

多執行緒內instance = new Instance(); 可能存在的一種執行時序圖:
在這里插入圖片描述

多執行緒執行時序圖

由于單執行緒內要遵守intra-thread semantics,從而保證執行緒A的執行結果不會被改變;但是在上圖多執行緒執行中,執行緒B可能讀到一個未正確完成初始化的Instance物件,
回到DoubleCheckedLocking這個示例代碼中,執行緒B可能在第一次instance == null判斷時為真,執行緒B接下來將訪問instance參考指向的物件,但是此時這個物件并沒有初始化完成,
?

多執行緒執行時序表:

時間執行緒A執行緒B
t1A1:分配物件的記憶體空間
t2A3:設定instance指向記憶體空間
t3B1:判斷instance是否為null
t4B2:由于instance不為null,執行緒B將訪問instance參考的物件
t5A2:初始化物件
t6A4:訪問instance參考的物件

2.3 分析問題關鍵點

有上述的時序圖表和解釋我們不難發現,出現的問題是物件instance實體化時指令重排序導致物件“逸出”了,因此我們有如下兩種解決思路:

  1. 不允許2和3重排序
  2. 運行2和3重排序,但是不允許其他執行緒“看到”這個重排序

下面講述具體實作方案,

3、基于volatile的解決方案

在DoubleCheckedLocking上做小修改即可(需要基于JDK1.5及以上)

package com.lizba.p1;

/**
 * <p>
 *      雙重檢查鎖正確示例,JDK1.5及以上
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 22:51
 */
public class DoubleCheckedLocking {

//    private static Instance instance;
    private volatile static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {                                 // 第一次檢查
            synchronized (DoubleCheckedLocking.class) {         // 加鎖
                if (instance == null) {                         // 第二次檢查
                    instance = new Instance();                  // instance為volatile,問題得以解決
                }
            }
        }
        return instance;
    }

}

宣告instance為volatile參考變數時,2和3的重排序會被禁止,執行時序圖如下:
在這里插入圖片描述

多執行緒執行時序圖

該方案是通過禁止重排序來實作,
?

4、基于類初始化的解決方案

JVM在類的初始化階段(即在Class被加載后,且被執行緒使用前),會執行類的初始化,在執行類的初始化期間,JVM會去獲取一個鎖,這個鎖可以同步多個執行緒對同一個類的初始化,
基于這個特性實作的方案被稱之為(Initialization On Demand Holder idiom),
示例代碼:

package com.lizba.p1;

/**
 * <p>
 *      實體工廠
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/12 23:52
 */
public class InstanceFactory {

    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance;
    }

}

假設執行緒A和執行緒B同時執行getInstance()方法,下面是執行示意圖:
在這里插入圖片描述

這個方案實質上是運行重排序,但是不允許非構造執行緒B看到未實體化完成的物件,利用了JVM類初始化的特性,
?

初始化一個類包括執行這個類的靜態初始化和初始化在這個類中宣告的靜態欄位,
那么類什么時候會被初始化呢?在Java語言規范中,首次發生如下情況中的任意一種,一個類或者一個介面型別T將會被立即初始化:

  1. T是一個類,而且一個T型別的實體被創建
  2. T是一個類,且T中宣告的一個靜態方法被呼叫
  3. T中宣告的一個靜態欄位被賦值
  4. T中宣告的一個靜態欄位被使用,而且這個欄位不是一個常量欄位
  5. T是一個頂級類(Top Level Class),而且一個斷言陳述句嵌套在T內部被執行

在InstanceFactory示例代碼中,符合情況4,InstanceHolder中靜態欄位instance被使用,導致觸發InstanceHolder物件的初始化,從而初始化Instance物件,
在Java代碼執行程序中,會存在多執行緒同時嘗試去初始化一個類或者一個介面,因此在Java語言規范中,會要求具體的JVM實作對這個程序做同步處理,(實作規范是每個類或者介面有一個唯一的初始化鎖LC與之對應,從C到LC的映射,由JVM去實作),
?

5、Java初始化類或介面的具體程序

我們來看看《Java并發編程藝術》的作者是如何通過5個步驟闡述這個程序的,

5.1 第一階段

通過在Class物件上同步(獲取Class物件的初始化鎖),來控制類或介面的初始化,這個獲取鎖的執行緒會一直等待,知道當前執行緒能夠獲取到這個Class物件的初始化鎖,
假設執行緒A和執行緒B同時初始化一個未被初始化的Class物件(初始化狀態state,此時被標記為state=noInitialization),圖示如下:
在這里插入圖片描述

類初始化-第一階段

類初始化-第一階段執行時序表:

時間執行緒A執行緒B
t1A1:嘗試獲取Class物件的初始化鎖,這里假設執行緒A獲取到初始化鎖,B1:嘗試獲取Class物件的初始化鎖,由于執行緒A獲取到了鎖,執行緒B等待獲取初始化鎖
t2A2:執行緒A看到物件還未被初始化(state=noInitialization),執行緒設定state=noInitializating
t3A3:執行緒A釋放初始化鎖

5.2 第二階段

執行緒A執行類的初始化,同時執行緒B在初始鎖對應的condition上等待,
圖示如下:
在這里插入圖片描述

類初始化-第2階段

類初始化-第二階段執行時序表:

時間執行緒A執行緒B
t1A1:執行類的靜態初始化和初始化類中宣告的靜態欄位B1:獲取到初始化鎖
t2B2:讀取到state=initializing
t3B3:釋放初始化鎖
t4B4:在初始化鎖的condition中等待

?

5.3 第三階段

執行緒A設定state=initialized,然后喚醒等待在condition上的所有執行緒
在這里插入圖片描述

類初始化-第3階段

類初始化-第三階段執行時序表:

時間執行緒A
t1A1:獲取初始化鎖
t2A2:設定state=initialized
t3A3:喚醒在condition中等待的所有執行緒
t4A4:釋放初始化鎖
t5A5:執行緒A的初始化程序完成

5.4 第四階段

執行緒B結束類的初始化處理
在這里插入圖片描述

類初始化-第4階段

類初始化-第四階段執行時序表:

時間執行緒B
t1B1:獲取初始化鎖
t2B2:讀取到state=initialized
t3B3:釋放初始化鎖
t4B4:執行緒B的類的初始化程序完成

第五階段

執行緒C執行類的初始化處理
在這里插入圖片描述

類初始化-第5階段

類初始化-第五階段執行時序表:

時間執行緒C
t1C1:獲取初始化鎖
t2C2:讀取到state=initialized
t3C3:釋放初始化鎖
t4C4:執行緒B的類的初始化程序完成

由于在第三階段已經完成了類的初始化,因此執行緒C執行類的初始化程序相對簡單,
?

6、總結

通過對比基于volatile的雙重鎖定的方案和基于類初始化的方案,發現使用類初始化的方案實作的代碼更加簡潔,但是基于volatile的雙重檢查鎖定的方案有一個額外的優點就是其不僅可以對靜態欄位實作延遲初始化,也可以對實體欄位實作延遲初始化(因為JVM類初始化這個方案只能初始化靜態欄位),欄位延遲初始化降低了初始化類和創建實體帶來的開銷,但也增加了訪問被延遲初始化的欄位的開銷,而在實際開發中正常的初始化要優于延遲初始化,
如果確定要進行延遲初始化,那么具體如何選擇呢?

  • 實體欄位延遲初始化使用volatile方案
  • 靜態欄位延遲初始化使用類初始化方案

?

文章總結至《Java并發編程藝術》,Java記憶體模型的總結到此就完全結束了,花費了不少晚上,雖然文章知識點來自書本,但是作者也做了如下作業:

  1. 文章的重點知識做了梳理和標記
  2. 對黑白圖片做了彩色畫圖,使其更加易懂
  3. 對部分繁瑣的知識點做了概括
  4. 對少部分錯誤的知識(主要是錯字)進行了勘誤
  5. 對每一句代碼做了全部重寫和注釋

碼字不易,多多關注,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287326.html

標籤:其他

上一篇:有沒有位元組工牌,Java并發安全的根本原因都得懂

下一篇:前端考核總結2

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more