摘要:關(guān)于對于重排序的講解,強(qiáng)烈推薦閱讀程曉明寫的深入理解內(nèi)存模型二重排序。語義語義單線程下,為了優(yōu)化可以對操作進(jìn)行重排序。編譯器和處理器為單個線程實現(xiàn)了語義,但對于多線程并不實現(xiàn)語義。雙重加載的單例模式分析即雙重檢查加鎖。
1. 引言版權(quán)聲明:本文由吳仙杰創(chuàng)作整理,轉(zhuǎn)載請注明出處:https://segmentfault.com/a/1190000009231182
在開始分析雙重加鎖單例代碼之前,我們需要先理解 java 內(nèi)存模式的重排序和無序?qū)懭胩匦浴?/p> 2. Java 內(nèi)存模型——重排序
在計算機(jī)中,軟件技術(shù)和硬件技術(shù)有一個共同的目標(biāo):在不改變程序執(zhí)行結(jié)果的前提下,盡可能的開發(fā)并行度。
同樣 Java 為了實現(xiàn)這一目標(biāo),在它的編譯和處理時會對代碼進(jìn)行重新排序,從而達(dá)到更高的并行度提升程序性能。
Java 在進(jìn)行重排序操作時會遵守數(shù)據(jù)依賴性,即編譯器和處理器不會改變存在數(shù)據(jù)依賴關(guān)系的兩個操作的執(zhí)行順序。
關(guān)于對于重排序的講解,強(qiáng)烈推薦閱讀程曉明寫的《深入理解Java內(nèi)存模型(二)——重排序》。
2.1 as-if-serial 語義as-if-serial 語義
: 單線程下,為了優(yōu)化可以對操作進(jìn)行重排序。
Java 編譯器和處理器為單個線程實現(xiàn)了 as-if-serial 語義,但對于多線程并不實現(xiàn) as-if-serial 語義。
2.2 無序?qū)懭?/b>若程序定義的變量之間沒有依賴關(guān)系,那么這兩個變量在 JVM 中的加載順序是不確定的。
3. 單例模式單例模式帶來的好處:
方便共享通用的資源。
避免頻繁操作共享資源所帶來的性能消耗。
而我們已單例模式有有餓漢式(static 變量,在類加載時就進(jìn)行初始化一次)與懶漢式(在使用到時才初始化一次)兩種,考慮到對于始初化單例類的開銷較大,往往我們需要創(chuàng)建單例是懶加載的,即在程序使用到單例時才創(chuàng)建,從而可以避免創(chuàng)建單例時拖慢程序的啟動速度。
所以對于使用單例模式有兩個要求:(1)懶加載。(2)多線程安全。
3.1 雙重加載的單例模式分析DCL(double-checked locking) 即雙重檢查加鎖。因為 DCL 模式的單例是懶加載的,所以這往往也是在許多項目中最容易見到的單例模式寫法。但是這種方式創(chuàng)建的單例,是多線程安全的嗎?
對于雙重檢查加載的單例代碼:
package com.wuxianjiezh.demo.threadpool; public class Singleton { private static Singleton instance; // 私有化的構(gòu)造方法,保證外部的類不能通過構(gòu)造器來實例化 private Singleton() { } // 雙重檢查加鎖來獲取對象單例 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
假設(shè)有二個線程要獲取上面的單例,當(dāng)其中 線程一 進(jìn)入同步塊執(zhí)行到 instance = new Singleton(); 時,線程二 來到了 鎖 外的第一個 null 判斷。注意這里,線程一 在執(zhí)行 instance = new Singleton(); 這段代碼時有以下幾個步驟,其中執(zhí)行是無序的(無序?qū)懭耄赡艹霈F(xiàn)下面這種情況:
// 1. 為 Singleton 對象分配內(nèi)存 memory = allocate(); // 2. 注意現(xiàn)在 instance 是非空的,但還沒初始化 instance = memory; // 3. 調(diào)用 Singleton 的構(gòu)造函數(shù),傳遞 instance ctorSingleton(instance);
當(dāng)在執(zhí)行到 instance = memory; 時,線程二 進(jìn)入了第一次的 null 判斷,此才 線程二 判斷 instance 不為 null,返回了 instance,但此時返回的不是單例的實例對象,而是內(nèi)存對象。
3.2 單例模式推薦寫法使用靜態(tài)內(nèi)部類:
package com.wuxianjiezh.demo.threadpool; public class Singleton { // 私有化的構(gòu)造方法,保證外部的類不能通過構(gòu)造器來實例化 private Singleton() { } // 靜態(tài)內(nèi)部類只會被加載一次 // 內(nèi)部類 SingletonHolder 只有在 getInstance() 方法第一次調(diào)用的時候才會被加載(實現(xiàn)了lazy) // 而且其加載過程是線程安全的(多線程安全) private static class SingletonHolder { // 單例變量 // 常規(guī)寫法 // private static final Singleton instance = new Singleton(); // 假設(shè)單例對象構(gòu)造方法會拋出異常時的寫法 private static final Singleton instance; static { instance = new Singleton(); } } // 獲取單例對象實例 public static Singleton getInstance() { return SingletonHolder.instance; } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69943.html
摘要:所以,在版本前,雙重檢查鎖形式的單例模式是無法保證線程安全的。 單例模式可能是代碼最少的模式了,但是少不一定意味著簡單,想要用好、用對單例模式,還真得費一番腦筋。本文對Java中常見的單例模式寫法做了一個總結(jié),如有錯漏之處,懇請讀者指正。 餓漢法 顧名思義,餓漢法就是在第一次引用該類的時候就創(chuàng)建對象實例,而不管實際是否需要創(chuàng)建。代碼如下: public class Singleton...
摘要:所有示例代碼請見下載于基本概念并發(fā)同時擁有兩個或者多個線程,如果程序在單核處理器上運行多個線程將交替地?fù)Q入或者換出內(nèi)存這些線程是同時存在的,每個線程都處于執(zhí)行過程中的某個狀態(tài),如果運行在多核處理器上此時,程序中的每個線程都 所有示例代碼,請見/下載于 https://github.com/Wasabi1234... showImg(https://upload-images.jians...
摘要:使用靜態(tài)類體現(xiàn)的是基于對象,而使用單例設(shè)計模式體現(xiàn)的是面向?qū)ο蟆6帉憜卫J降拇a編寫單例模式的代碼其實很簡單,就分了三步將構(gòu)造函數(shù)私有化在類的內(nèi)部創(chuàng)建實例提供獲取唯一實例的方法餓漢式根據(jù)上面的步驟,我們就可以輕松完成創(chuàng)建單例對象了。 前言 只有光頭才能變強(qiáng) 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡單啦 本來打算沒那么快更新的,這陣子在刷Spring的書籍。在看...
摘要:假設(shè)不發(fā)生編譯器重排和指令重排,線程修改了的值,但是修改以后,的值可能還沒有寫回到主存中,那么線程得到就是很自然的事了。同理,線程對于的賦值操作也可能沒有及時刷新到主存中。線程的最后操作與線程發(fā)現(xiàn)線程已經(jīng)結(jié)束同步。 很久沒更新文章了,對隔三差五過來刷更新的讀者說聲抱歉。 關(guān)于 Java 并發(fā)也算是寫了好幾篇文章了,本文將介紹一些比較基礎(chǔ)的內(nèi)容,注意,閱讀本文需要一定的并發(fā)基礎(chǔ)。 本文的...
閱讀 1214·2021-09-26 09:55
閱讀 3159·2019-08-30 15:55
閱讀 949·2019-08-30 15:53
閱讀 2286·2019-08-30 13:59
閱讀 2367·2019-08-29 13:08
閱讀 1098·2019-08-29 12:19
閱讀 3290·2019-08-26 13:41
閱讀 411·2019-08-26 13:24