Java中雙重檢查鎖(double checked locking)的正確實(shí)現(xiàn)
前言
在實(shí)現(xiàn)單例模式時(shí),如果未考慮多線程的情況,就容易寫出下面的錯誤代碼:
public class Singleton { private static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } return uniqueSingleton; } }
在多線程的情況下,這樣寫可能會導(dǎo)致uniqueSingleton有多個實(shí)例。比如下面這種情況,考慮有兩個線程同時(shí)調(diào)用getInstance():
Time | Thread A | Thread B |
---|---|---|
T1 | 檢查到uniqueSingleton為空 | |
T2 | 檢查到uniqueSingleton為空 | |
T3 | 初始化對象A | |
T4 | 返回對象A | |
T5 | 初始化對象B | |
T6 | 返回對象B |
可以看到,uniqueSingleton被實(shí)例化了兩次并且被不同對象持有。完全違背了單例的初衷。
加鎖
出現(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; } }
這樣雖然解決了問題,但是因?yàn)橛玫搅藄ynchronized,會導(dǎo)致很大的性能開銷,并且加鎖其實(shí)只需要在第一次初始化的時(shí)候用到,之后的調(diào)用都沒必要再進(jìn)行加鎖。
雙重檢查鎖
雙重檢查鎖(double checked locking)是對上述問題的一種優(yōu)化。先判斷對象是否已經(jīng)被初始化,再決定要不要加鎖。
錯誤的雙重檢查鎖
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; } }
如果這樣寫,運(yùn)行順序就成了:
- 檢查變量是否被初始化(不去獲得鎖),如果已被初始化則立即返回。
- 獲取鎖。
- 再次檢查變量是否已經(jīng)被初始化,如果還沒被初始化就初始化一個對象。
執(zhí)行雙重檢查是因?yàn)?,如果多個線程同時(shí)了通過了第一次檢查,并且其中一個線程首先通過了第二次檢查并實(shí)例化了對象,那么剩余通過了第一次檢查的線程就不會再去實(shí)例化對象。
這樣,除了初始化的時(shí)候會出現(xiàn)加鎖的情況,后續(xù)的所有調(diào)用都會避免加鎖而直接返回,解決了性能消耗的問題。
隱患
上述寫法看似解決了問題,但是有個很大的隱患。實(shí)例化對象的那行代碼(標(biāo)記為error的那行),實(shí)際上可以分解成以下三個步驟:
- 分配內(nèi)存空間
- 初始化對象
- 將對象指向剛分配的內(nèi)存空間
但是有些編譯器為了性能的原因,可能會將第二步和第三步進(jìn)行重排序,順序就成了:
- 分配內(nèi)存空間
- 將對象指向剛分配的內(nèi)存空間
- 初始化對象
現(xiàn)在考慮重排序后,兩個線程發(fā)生了以下調(diào)用:
Time | Thread A | Thread B |
---|---|---|
T1 | 檢查到uniqueSingleton為空 | |
T2 | 獲取鎖 | |
T3 | 再次檢查到uniqueSingleton為空 | |
T4 | 為uniqueSingleton分配內(nèi)存空間 | |
T5 | 將uniqueSingleton指向內(nèi)存空間 | |
T6 | 檢查到uniqueSingleton不為空 | |
T7 | 訪問uniqueSingleton(此時(shí)對象還未完成初始化) | |
T8 | 初始化uniqueSingleton |
在這種情況下,T7時(shí)刻線程B對uniqueSingleton的訪問,訪問的是一個初始化未完成的對象。
正確的雙重檢查鎖
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; } }
為了解決上述問題,需要在uniqueSingleton前加入關(guān)鍵字volatile。使用了volatile關(guān)鍵字后,重排序被禁止,所有的寫(write)操作都將發(fā)生在讀(read)操作之前。
至此,雙重檢查鎖就可以完美工作了。
總結(jié)
到此這篇關(guān)于Java中雙重檢查鎖(double checked locking)的文章就介紹到這了,更多相關(guān)Java雙重檢查鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
參考資料:
- 雙重檢查鎖定模式
- 如何在Java中使用雙重檢查鎖實(shí)現(xiàn)單例:http://www.importnew.com/12196.html
- 雙重檢查鎖定與延遲初始化
- Java并發(fā)編程之阻塞隊(duì)列(BlockingQueue)詳解
- Java并發(fā)編程之ReentrantLock實(shí)現(xiàn)原理及源碼剖析
- java中synchronized Lock(本地同步)鎖的8種情況
- 詳解Java多線程tryLock()方法使用
- java并發(fā)編程工具類PriorityBlockingQueue優(yōu)先級隊(duì)列
- Java中提供synchronized后為什么還要提供Lock
- Java并發(fā)編程之StampedLock鎖介紹
- Java中l(wèi)ock和tryLock及l(fā)ockInterruptibly的區(qū)別
相關(guān)文章
SpringSecurity授權(quán)實(shí)現(xiàn)基本思路
本文介紹了SpringSecurity中使用FilterSecurityInterceptor進(jìn)行權(quán)限校驗(yàn)的基本方法,通過SecurityContextHolder獲取Authentication中的權(quán)限信息,感興趣的朋友跟隨小編一起看看吧2024-10-10Springboot Druid 自定義加密數(shù)據(jù)庫密碼的幾種方案
這篇文章主要介紹了Springboot Druid 自定義加密數(shù)據(jù)庫密碼的步驟,幫助大家更好的理解和使用springboot,感興趣的朋友可以了解下2020-12-12Spring?Boot統(tǒng)一處理全局異常的實(shí)戰(zhàn)教程
最近在做項(xiàng)目時(shí)需要對異常進(jìn)行全局統(tǒng)一處理,所以下面這篇文章主要給大家介紹了關(guān)于Spring?Boot統(tǒng)一處理全局異常的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-12-12解讀SpringBoot接收List<Bean>參數(shù)問題(POST請求方式)
這篇文章主要介紹了解讀SpringBoot接收List<Bean>參數(shù)問題(POST請求方式),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09Mybatis 傳輸List的實(shí)現(xiàn)代碼
本文通過實(shí)例代碼給大家介紹了mybatis傳輸list的實(shí)現(xiàn)代碼,非常不錯,具有參考借鑒價(jià)值,需要的朋友參考下吧2017-09-09Springboot項(xiàng)目因?yàn)閗ackson版本問題啟動報(bào)錯解決方案
這篇文章主要介紹了Springboot項(xiàng)目因?yàn)閗ackson版本問題啟動報(bào)錯解決方案,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Spring?this調(diào)用當(dāng)前類方法無法攔截的示例代碼
這篇文章主要介紹了Spring?this調(diào)用當(dāng)前類方法無法攔截,通過debug 查看這個proxyService1 和this的區(qū)別,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-03-03詳解Java利用ExecutorService實(shí)現(xiàn)同步執(zhí)行大量線程
這篇文章主要介紹了Java利用ExecutorService實(shí)現(xiàn)同步執(zhí)行大量線程,ExecutorService可以維護(hù)我們的大量線程在操作臨界資源時(shí)的穩(wěn)定性。2017-03-03Java實(shí)現(xiàn)Timer的定時(shí)調(diào)度函數(shù)schedule的四種用法
本文主要介紹了Java實(shí)現(xiàn)Timer的定時(shí)調(diào)度函數(shù)schedule的四種用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04