溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么解決Java單例模式中的線程安全問題

發(fā)布時(shí)間:2023-05-12 11:40:11 來源:億速云 閱讀:92 作者:iii 欄目:編程語言

這篇文章主要介紹了怎么解決Java單例模式中的線程安全問題的相關(guān)知識,內(nèi)容詳細(xì)易懂,操作簡單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇怎么解決Java單例模式中的線程安全問題文章都會有所收獲,下面我們一起來看看吧。

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

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

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

    考慮線程安全:

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

    二. 單例模式

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

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

    1. 餓漢模式

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

    實(shí)現(xiàn)代碼:

    public class Singleton {
        private static Singleton instance = new Singleton();
        private Singleton(){
     
        }
        public static Singleton getInstance(){
            return instance;
        }
    }
    2. 懶漢模式

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

    實(shí)現(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)實(shí)例沒有被創(chuàng)建的時(shí)候,如果有多個(gè)線程都調(diào)用getInstance方法,就可能創(chuàng)建多個(gè)實(shí)例,就存在線程安全問題
    但是實(shí)例一旦創(chuàng)建好,后面線程調(diào)用getInstance方法就不會出現(xiàn)線程安全問題

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

    3. 懶漢模式(使用synchronized改進(jìn))

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

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

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

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

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

    4. 懶漢模式(使用雙重校驗(yàn)鎖改進(jìn))

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

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

    實(shí)現(xiàn)代碼:

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

    對雙重if的解析:

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

    畫圖分析,如下所示:

    怎么解決Java單例模式中的線程安全問題

    三. volatile的原理

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

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

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

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

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

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

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

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

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

    使用volatile保證有序性后:

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

    關(guān)于“怎么解決Java單例模式中的線程安全問題”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“怎么解決Java單例模式中的線程安全問題”知識都有一定的了解,大家如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI