溫馨提示×

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

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

Java中如何實(shí)現(xiàn)雙重檢查鎖

發(fā)布時(shí)間:2021-09-14 17:30:50 來(lái)源:億速云 閱讀:105 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹了Java中如何實(shí)現(xiàn)雙重檢查鎖,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

    前言

    在實(shí)現(xiàn)單例模式時(shí),如果未考慮多線程的情況,就容易寫(xiě)出下面的錯(cuò)誤代碼:

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

    在多線程的情況下,這樣寫(xiě)可能會(huì)導(dǎo)致uniqueSingleton有多個(gè)實(shí)例。比如下面這種情況,考慮有兩個(gè)線程同時(shí)調(diào)用getInstance():

    TimeThread AThread B
    T1檢查到uniqueSingleton為空
    T2
    檢查到uniqueSingleton為空
    T3
    初始化對(duì)象A
    T4
    返回對(duì)象A
    T5初始化對(duì)象B
    T6返回對(duì)象B

    可以看到,uniqueSingleton被實(shí)例化了兩次并且被不同對(duì)象持有。完全違背了單例的初衷。

    加鎖

    出現(xiàn)這種情況,第一反應(yīng)就是加鎖,如下:

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

    這樣雖然解決了問(wèn)題,但是因?yàn)橛玫搅藄ynchronized,會(huì)導(dǎo)致很大的性能開(kāi)銷(xiāo),并且加鎖其實(shí)只需要在第一次初始化的時(shí)候用到,之后的調(diào)用都沒(méi)必要再進(jìn)行加鎖。

    雙重檢查鎖

    雙重檢查鎖(double checked locking)是對(duì)上述問(wèn)題的一種優(yōu)化。先判斷對(duì)象是否已經(jīng)被初始化,再?zèng)Q定要不要加鎖。

    錯(cuò)誤的雙重檢查鎖

    public class Singleton {
        private static Singleton uniqueSingleton;
    
        private Singleton() {
        }
    
        public Singleton getInstance() {
            if (null == uniqueSingleton) {
                synchronized (Singleton.class) {
                    if (null == uniqueSingleton) {
                        uniqueSingleton = new Singleton();   // error
                    }
                }
            }
            return uniqueSingleton;
        }
    }

    如果這樣寫(xiě),運(yùn)行順序就成了:

    • 檢查變量是否被初始化(不去獲得鎖),如果已被初始化則立即返回。

    • 獲取鎖。

    • 再次檢查變量是否已經(jīng)被初始化,如果還沒(méi)被初始化就初始化一個(gè)對(duì)象。

    執(zhí)行雙重檢查是因?yàn)椋绻鄠€(gè)線程同時(shí)了通過(guò)了第一次檢查,并且其中一個(gè)線程首先通過(guò)了第二次檢查并實(shí)例化了對(duì)象,那么剩余通過(guò)了第一次檢查的線程就不會(huì)再去實(shí)例化對(duì)象。

    這樣,除了初始化的時(shí)候會(huì)出現(xiàn)加鎖的情況,后續(xù)的所有調(diào)用都會(huì)避免加鎖而直接返回,解決了性能消耗的問(wèn)題。

    隱患

    上述寫(xiě)法看似解決了問(wèn)題,但是有個(gè)很大的隱患。實(shí)例化對(duì)象的那行代碼(標(biāo)記為error的那行),實(shí)際上可以分解成以下三個(gè)步驟:

    1. 分配內(nèi)存空間

    2. 初始化對(duì)象

    3. 將對(duì)象指向剛分配的內(nèi)存空間

    但是有些編譯器為了性能的原因,可能會(huì)將第二步和第三步進(jìn)行重排序,順序就成了:

    1. 分配內(nèi)存空間

    2. 將對(duì)象指向剛分配的內(nèi)存空間

    3. 初始化對(duì)象

    現(xiàn)在考慮重排序后,兩個(gè)線程發(fā)生了以下調(diào)用:

    TimeThread AThread B
    T1檢查到uniqueSingleton為空
    T2獲取鎖
    T3再次檢查到uniqueSingleton為空
    T4為uniqueSingleton分配內(nèi)存空間
    T5將uniqueSingleton指向內(nèi)存空間
    T6
    檢查到uniqueSingleton不為空
    T7
    訪問(wèn)uniqueSingleton(此時(shí)對(duì)象還未完成初始化)
    T8初始化uniqueSingleton

    在這種情況下,T7時(shí)刻線程B對(duì)uniqueSingleton的訪問(wèn),訪問(wèn)的是一個(gè)初始化未完成的對(duì)象。

    正確的雙重檢查鎖

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

    為了解決上述問(wèn)題,需要在uniqueSingleton前加入關(guān)鍵字volatile。使用了volatile關(guān)鍵字后,重排序被禁止,所有的寫(xiě)(write)操作都將發(fā)生在讀(read)操作之前。

    至此,雙重檢查鎖就可以完美工作了。

    感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“Java中如何實(shí)現(xiàn)雙重檢查鎖”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!

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

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

    AI