欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java單例模式中的線程安全問題

 更新時間:2022年06月20日 10:09:52   作者:XH學(xué)Java  
本文主要介紹了Java單例模式中的線程安全問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一. 使用多線程需要考慮的因素

提高效率:使用多線程就是為了充分利用CPU資源,提高任務(wù)的效率
線程安全:使用多線程最基本的就是保障線程安全問題

所以我們在設(shè)計多線程代碼的時候就必須在滿足線程安全的前提下盡可能的提高任務(wù)執(zhí)行的效
故:
加鎖細(xì)粒度化:加鎖的代碼少一點,讓其他代碼可以并發(fā)并行的執(zhí)行

??考慮線程安全:

沒有操作共享變量的代碼沒有安全問題
對共享變量的讀,使用volatile修飾變量即可
對共享變量的寫,使用synchronized加鎖

??二. 單例模式

單例模式能保證某個類在程序中只存在唯一一份實例,而不會創(chuàng)建出多個實例
例如:DataSource(數(shù)據(jù)連接池),一個數(shù)據(jù)庫只需要一個連接池對象

單例模式分為餓漢模式和懶漢模式

??1. 餓漢模式

餓漢模式是在類加載的時候就創(chuàng)建實例
這種方式是滿足線程安全的(JVM內(nèi)部使用了加鎖,即多個線程調(diào)用靜態(tài)方法,只有一個線程競爭到鎖并且完成創(chuàng)建,只執(zhí)行一次)

實現(xiàn)代碼:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton(){
 
    }
    public static Singleton getInstance(){
        return instance;
    }
}

??2. 懶漢模式

懶漢模式是在類加載的時候不創(chuàng)建實例,第一次使用的時候才創(chuàng)建

實現(xiàn)代碼:

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

觀察上述代碼,在單線程下不存在線程安全問題,但是在多線程環(huán)境下存在安全問題嗎? 

分析:
??當(dāng)實例沒有被創(chuàng)建的時候,如果有多個線程都調(diào)用getInstance方法,就可能創(chuàng)建多個實例,就存在線程安全問題 
??但是實例一旦創(chuàng)建好,后面線程調(diào)用getInstance方法就不會出現(xiàn)線程安全問題

結(jié)果:線程安全問題出現(xiàn)在首次創(chuàng)建實例的時候

??3. 懶漢模式(使用synchronized改進)

我們使用sychronized修飾,??‍???代碼如下:

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

這樣實現(xiàn)線程安全存在什么問題呢?

解析:
我們對方法使用synchronized修飾,也就是每次調(diào)用該方法的時候都會競爭鎖,但是創(chuàng)建實例只需要創(chuàng)建一次,也就是創(chuàng)建實例后,再調(diào)用該方法還需要競爭鎖釋放鎖

結(jié)果:雖然滿足線程安全,但是效率低

4. 懶漢模式(使用雙重校驗鎖改進)

在上述代碼的基礎(chǔ)上進行改動:

使用雙重if判定,降低競爭鎖頻率
使用volatile修飾instance 

實現(xiàn)代碼:

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton(){
 
    }
    public static synchronized Singleton getInstance(){
        if(instance == null){ //外層的if判斷:如果實例被創(chuàng)建直接return,不讓線程再繼續(xù)競爭鎖
            //在沒有創(chuàng)建實例時,多個線程已經(jīng)進入if判斷了
            //一個線程競爭到鎖,其他線程阻塞等待
            synchronized (Singleton.class) {
                //內(nèi)層的if判斷,目的是讓競爭失敗的鎖如果再次競爭成功的話判斷實例是否被創(chuàng)建,創(chuàng)建釋放鎖return,沒有則創(chuàng)建
                if(instance == null){ 
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

??對雙重if的解析:

??外層的if判斷:實例只是被創(chuàng)建一次,當(dāng)實例已經(jīng)被創(chuàng)建好了就不要后續(xù)操作,直接return返回
??內(nèi)層的if判斷:實例未被創(chuàng)建時,多個線程同時競爭鎖,只有一個線程競爭成功并創(chuàng)建實例,其他競爭失敗的線程就會阻塞等待,當(dāng)?shù)谝痪€程釋放鎖后,這些競爭失敗的線程就會繼續(xù)競爭,但是實例已經(jīng)創(chuàng)建好了,所以需要再次進行if判斷 

畫圖分析,如下所示:

三. volatile的原理 

volatile保證了可見性,有序性,在Java層面看,volatile是無鎖操作,多個線程對volatile修飾的變量進行讀可以并發(fā)并行執(zhí)行,和無鎖執(zhí)行效率差不多

volatile修飾的變量中,CPU使用了緩存一致性協(xié)議來保證讀取的都是最新的主存數(shù)據(jù) 

緩存一致性:如果有別的線程修改了volatile修飾的變量,就會把CPU緩存中的變量置為無效,要操作這個變量就要從主存中重新讀取

??四. volatile的擴展問題(了解)

??如果說volatile不保證有序性,雙重校驗鎖的寫法是否有問題?

關(guān)于new對象按順序分為3條指令:

??(1) 分配對象的內(nèi)存空間
??(2) 實例化對象
??(3) 賦值給變量

正常的執(zhí)行順序為(1)(2)(3),JVM可能會優(yōu)化進行重排序后的順序為(1)(3)(2)

這個重排序的結(jié)果可能導(dǎo)致分配內(nèi)存空間后,對象還沒有實例化完成,就完成了賦值
在這個錯誤的賦值后,instance==null不成立,線程就會拿著未完成實例化的instance,使用它的屬性和方法就會出錯

使用volatile保證有序性后:

線程在new對象時不管(1)(2)(3)是什么順序,后續(xù)線程拿到的instance是已經(jīng)實例化完成的
CPU里邊,基于volatile變量操作是有CPU級別的加鎖機制(它保證(1)(2)(3)全部執(zhí)行完,寫回主存,再執(zhí)行其他線程對該變量的操作)

 到此這篇關(guān)于Java單例模式中的線程安全問題的文章就介紹到這了,更多相關(guān)Java單例模式線程安全內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論