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