国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從單例模式到HappensBefore

v1 / 476人閱讀

摘要:但是有引入了新的問題線程不安全,返回的對象可能還沒有初始化。如果只有一個線程調(diào)用是沒有問題的因為不管步驟如何調(diào)換,保證返回的對象是已經(jīng)構(gòu)造好了。這種特殊情況稱之為指令重排序采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)電路單元處理。

目錄

雙重檢測鎖的演變過程

利用HappensBefore分析并發(fā)問題

無volatile的雙重檢測鎖

雙重檢測鎖的演變過程 synchronized修飾方法的單例模式

雙重檢測鎖的最初形態(tài)是通過在方法聲明的部分加上synchronized進行同步,保證同一時間調(diào)用方法的線程只有一個,從而保證new Singlton()的線程安全:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

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

這樣做的好處是代碼簡單、并且JVM保證new Singlton()這行代碼線程安全。但是付出的代價有點高昂:
所有的線程的每一次調(diào)用都是同步調(diào)用,性能開銷很大,而且new Singlton()只會執(zhí)行一次,不需要每一次都進行同步。

既然只需要在new Singlton()時進行同步,那么把synchronized的同步范圍縮小呢?

線程不安全的雙重檢測鎖
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;
    }
}

synchronized同步的范圍縮小以后,貌似是解決了每次調(diào)用都需要進行同步而導(dǎo)致的性能開銷的問題。但是有引入了新的問題:線程不安全,返回的對象可能還沒有初始化。

深入到字節(jié)碼的層面來看看下面這段代碼:

instance = new Singleton()
returen instance;

正常情況下JVM編譯成成字節(jié)碼,它是這樣的:

step.1 new:開辟一塊內(nèi)存空間
step.2 invokespecial:執(zhí)行初始化方法,對內(nèi)存進行初始化
step.3 putstatic:將該內(nèi)存空間的引用賦值給instance
step.4 areturn:方法執(zhí)行結(jié)束,返回instance

當然這里限定在正常情況下,在特殊情況下也可以編譯成這樣:

step.1 new:開辟一塊內(nèi)存空間
step.3 putstatic:將該內(nèi)存空間的引用賦值給instance
step.2 invokespecial:執(zhí)行初始化方法,對內(nèi)存進行初始化
step.4 areturn:方法執(zhí)行結(jié)束,返回instance

步驟2和步驟3進行了調(diào)換:先執(zhí)行步驟3再執(zhí)行步驟2。

如果只有一個線程調(diào)用是沒有問題的:因為不管步驟如何調(diào)換,JVM保證返回的對象是已經(jīng)構(gòu)造好了。

如果同時有多個線程調(diào)用,那么部分調(diào)用線程返回的對象有可能是沒有構(gòu)造好的對象。

這種特殊情況稱之為:指令重排序:CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)電路單元處理。當然不是亂排序,重排序保證CPU能夠正確處理指令依賴情況以保障程序能夠得出正確的執(zhí)行結(jié)果。

利用HappensBefore分析并發(fā)問題 什么是HappensBefore

HappensBefore:先行發(fā)生,是

判斷數(shù)據(jù)是否存在競爭、線程是否安全的重要依據(jù)

A happens-beforeB,那么A對B可見(A做的操作對B可見)

是一種偏序關(guān)系。hb(a,b),hb(b,c) => hb(a,c)

換句話說,可以通過HappensBefore推斷代碼在多線程下是否線程安全

舉一個《深入理解Java虛擬機》上的例子:

//以下操作在線程A中執(zhí)行
int i = 1;

//以下操作在線程B中執(zhí)行
j = i;

//以下操作在線程C中執(zhí)行
i = 2;

如果hb(i=1,j=i),那么可以確定變量j的值一定等于1。得出這個結(jié)論的依據(jù)有兩個:

根據(jù)HappensBefore的規(guī)則,i=1的結(jié)果可以被j=i觀察到

線程C還沒有登場

如果線程C的執(zhí)行時間在線程A和線程B之間,那么j的值是多少呢?答案是不確定!因為線程C和線程B之間沒有HappensBefore的關(guān)系:線程C對變量的i的更改可能被線程B觀察到也可能不會!

HappensBefore關(guān)系

這些是“天然的”、JVM保證的HappensBefore關(guān)系:

程序次序規(guī)則

管程鎖定規(guī)則

volatile變量規(guī)則

線程啟動規(guī)則

線程終止規(guī)則

線程中斷規(guī)則

對象終結(jié)規(guī)則

傳遞性

重點介紹程序次序規(guī)則管程鎖定規(guī)則volatile變量規(guī)則傳遞性,后面分析需要用到這四個性質(zhì):

程序次序規(guī)則:在一個線程內(nèi),按照程序控制流順序,書寫在前面的操作HappensBefore書寫在后面的操作

管程鎖定規(guī)則:對于同一個鎖來說,在時間順序上,上一個unlock操作HappensBefore下一個lock操作

volatile變量規(guī)則:對于一個volatile修飾的變量,在時間順序上,寫操作HappensBefore讀操作

傳遞性:hb(a,b),hb(b,c) => hb(a,c)

分析之前線程不安全的雙重檢測鎖
public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {                     //1
            synchronized (Singleton.class) {        //2
                if (instance == null) {             //3
                    instance = new Singleton();     //4
                    new                             //4.1
                    invokespecial                   //4.2
                    pustatic                        //4.3
                }
            }
        }
        return instance;                            //5
    }
}

經(jīng)過上面的討論,已經(jīng)知道因為JVM重排序?qū)е?b>代碼4.2提前執(zhí)行了,導(dǎo)致后面一個線程執(zhí)行代碼1返回的值為false,進而直接返回了還沒有構(gòu)造好的instance對象:

線程1 線程2
1
2
3
4.1
4.3
1
5
4.2
5

通過表格,可能清晰看到問題所在:線程1代碼4.3 執(zhí)行后,線程2執(zhí)行代碼1讀到了臟數(shù)據(jù)。要想不讀到臟數(shù)據(jù),只要證明存在hb(T1-4.3,T2-1)(T1-4表示線程1代碼4,T2-1表示線程2代碼1,下同),那么是否存在呢?很遺憾,不存在:

程序次序規(guī)則:不在同一個線程

管程鎖定規(guī)則:線程2沒有嘗試lock

volatile變量規(guī)則:instance對象沒有通過volatile關(guān)鍵字修飾

傳遞性:不存在

用HappensBefore分析,可以很清晰、明確看到?jīng)]有volatile修飾的雙重檢測鎖是線程不安全的。但,真的是這樣的嗎?

無volatile的雙重檢測鎖

在第二部分,通過HappensBefore分析沒有volatile修飾的雙重檢測鎖是線程不安全,那只有用volatile修飾的雙重檢測鎖才是線程安全的嗎?答案是否定的。

用volatile關(guān)鍵字修飾的本質(zhì)是想利用volatile變量規(guī)則,使得寫操作(T1-4)HappensBefore讀操作(T2-1),那只要另找一條HappensBefore規(guī)則保證即可。答案是程序次序規(guī)則管程鎖定規(guī)則

先看代碼:

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {                         //1
            synchronized (Singleton.class) {            //2
                if (instance == null) {                 //3
                    Singleton temp = new Singleton();   //4
                    temp.toString();                    //5
                    instance = temp;                    //6
                }
            }
        }
        return instance;                                //7
    }
}

在原有的基礎(chǔ)上加了兩行代碼:

instance = new Singleton();           //4

Singleton temp = new Singleton();   //4
temp.toString();                    //5
instance = temp;                    //6

為什么要這么做?
通過管程鎖定規(guī)則保證執(zhí)行到代碼6時,temp對象已經(jīng)構(gòu)造好了。想一想,為什么?

其他線程執(zhí)行代碼1時,如果能夠觀察到T1-6的寫操作,那么直接返回instance對象

如果沒有觀察到T1-6的寫操作,那么嘗試獲取鎖,此時管程鎖定規(guī)則開始生效:保證當前線程一定能夠觀察到T1-6操作

執(zhí)行流程可能是這樣的:

線程1 線程2 線程3
1
1
2
3
4
5
6
2
3
1 7
7
7

無論怎樣執(zhí)行,其他線程都能夠觀察到T1-6的寫操作

其他 volatile、synchronized為什么可以禁止JVM重排序

內(nèi)存屏障。

JVM在凡是有volatile、synchronized出現(xiàn)的地方都加了一道內(nèi)存屏障:重排序時,不可以把內(nèi)存屏障后面的指令重排序到內(nèi)存屏障前面執(zhí)行,并且會及時的將線程工作內(nèi)存中的數(shù)據(jù)及時更新到主內(nèi)存中,進而使得其他的線程能夠觀察到最新的數(shù)據(jù)

參考資料

《深入理解Java虛擬機》

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/74288.html

相關(guān)文章

  • php應(yīng)用單例模式實現(xiàn)一個數(shù)據(jù)庫類

    摘要:代碼實現(xiàn)單例模式靜態(tài)變量保存全局實例私有構(gòu)造函數(shù),防止外界實例化對象私有克隆函數(shù),防止外界克隆對象靜態(tài)方法,單例統(tǒng)一訪問路口單例模式的優(yōu)缺點優(yōu)點改進系統(tǒng)的設(shè)計是對全局變量的一種改進缺點難于調(diào)試隱藏的依賴關(guān)系無法用錯誤類型的數(shù)據(jù)覆寫一個單例 單例模式(Singleton Pattern 單件模式或單元素模式)單例模式有以下3個特點:1、一個類只能有一個類對象(只能實例化一個對象)2、它必...

    EddieChan 評論0 收藏0
  • php應(yīng)用單例模式實現(xiàn)一個數(shù)據(jù)庫類

    摘要:代碼實現(xiàn)單例模式靜態(tài)變量保存全局實例私有構(gòu)造函數(shù),防止外界實例化對象私有克隆函數(shù),防止外界克隆對象靜態(tài)方法,單例統(tǒng)一訪問路口單例模式的優(yōu)缺點優(yōu)點改進系統(tǒng)的設(shè)計是對全局變量的一種改進缺點難于調(diào)試隱藏的依賴關(guān)系無法用錯誤類型的數(shù)據(jù)覆寫一個單例 單例模式(Singleton Pattern 單件模式或單元素模式)單例模式有以下3個特點:1、一個類只能有一個類對象(只能實例化一個對象)2、它必...

    GHOST_349178 評論0 收藏0
  • php應(yīng)用單例模式實現(xiàn)一個數(shù)據(jù)庫類

    摘要:代碼實現(xiàn)單例模式靜態(tài)變量保存全局實例私有構(gòu)造函數(shù),防止外界實例化對象私有克隆函數(shù),防止外界克隆對象靜態(tài)方法,單例統(tǒng)一訪問路口單例模式的優(yōu)缺點優(yōu)點改進系統(tǒng)的設(shè)計是對全局變量的一種改進缺點難于調(diào)試隱藏的依賴關(guān)系無法用錯誤類型的數(shù)據(jù)覆寫一個單例 單例模式(Singleton Pattern 單件模式或單元素模式)單例模式有以下3個特點:1、一個類只能有一個類對象(只能實例化一個對象)2、它必...

    yhaolpz 評論0 收藏0
  • JavaScript設(shè)計模式--單例模式

    摘要:的構(gòu)造函數(shù)實際上負責了兩件事情。有一個缺點,假如我們某天需要利用這個類,在頁面中創(chuàng)建千千萬萬個,即要這個類從單例類變成一個普通的可產(chǎn)生多個實例的類,那我們就要改寫構(gòu)造函數(shù),把控制創(chuàng)建唯一對象的那一段去掉,這樣會給我們帶來不必要的麻煩。 定義:單例模式保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。 單例模式是一種常用的模式,有一些對象我們往往只需要一個,比如線程池、全局緩存、瀏覽...

    canopus4u 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<