您好,登錄后才能下訂單哦!
這篇文章主要介紹了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():
Time | Thread A | Thread 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定要不要加鎖。
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è)步驟:
分配內(nèi)存空間
初始化對(duì)象
將對(duì)象指向剛分配的內(nèi)存空間
但是有些編譯器為了性能的原因,可能會(huì)將第二步和第三步進(jìn)行重排序,順序就成了:
分配內(nèi)存空間
將對(duì)象指向剛分配的內(nèi)存空間
初始化對(duì)象
現(xiàn)在考慮重排序后,兩個(gè)線程發(fā)生了以下調(diào)用:
Time | Thread A | Thread 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í)!
免責(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)容。