Java中雙重檢查鎖(double checked locking)的正確實現(xiàn)
前言
在實現(xiàn)單例模式時,如果未考慮多線程的情況,就容易寫出下面的錯誤代碼:
public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
在多線程的情況下,這樣寫可能會導致uniqueSingleton有多個實例。比如下面這種情況,考慮有兩個線程同時調(diào)用getInstance():
| Time | Thread A | Thread B |
|---|---|---|
| T1 | 檢查到uniqueSingleton為空 | |
| T2 | 檢查到uniqueSingleton為空 | |
| T3 | 初始化對象A | |
| T4 | 返回對象A | |
| T5 | 初始化對象B | |
| T6 | 返回對象B |
可以看到,uniqueSingleton被實例化了兩次并且被不同對象持有。完全違背了單例的初衷。
加鎖
出現(xiàn)這種情況,第一反應就是加鎖,如下:
public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {
}
public synchronized Singleton getInstance() {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
這樣雖然解決了問題,但是因為用到了synchronized,會導致很大的性能開銷,并且加鎖其實只需要在第一次初始化的時候用到,之后的調(diào)用都沒必要再進行加鎖。
雙重檢查鎖
雙重檢查鎖(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;
}
}
如果這樣寫,運行順序就成了:
- 檢查變量是否被初始化(不去獲得鎖),如果已被初始化則立即返回。
- 獲取鎖。
- 再次檢查變量是否已經(jīng)被初始化,如果還沒被初始化就初始化一個對象。
執(zhí)行雙重檢查是因為,如果多個線程同時了通過了第一次檢查,并且其中一個線程首先通過了第二次檢查并實例化了對象,那么剩余通過了第一次檢查的線程就不會再去實例化對象。
這樣,除了初始化的時候會出現(xiàn)加鎖的情況,后續(xù)的所有調(diào)用都會避免加鎖而直接返回,解決了性能消耗的問題。
隱患
上述寫法看似解決了問題,但是有個很大的隱患。實例化對象的那行代碼(標記為error的那行),實際上可以分解成以下三個步驟:
- 分配內(nèi)存空間
- 初始化對象
- 將對象指向剛分配的內(nèi)存空間
但是有些編譯器為了性能的原因,可能會將第二步和第三步進行重排序,順序就成了:
- 分配內(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(此時對象還未完成初始化) | |
| T8 | 初始化uniqueSingleton |
在這種情況下,T7時刻線程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前加入關鍵字volatile。使用了volatile關鍵字后,重排序被禁止,所有的寫(write)操作都將發(fā)生在讀(read)操作之前。
至此,雙重檢查鎖就可以完美工作了。
總結
到此這篇關于Java中雙重檢查鎖(double checked locking)的文章就介紹到這了,更多相關Java雙重檢查鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
參考資料:
- 雙重檢查鎖定模式
- 如何在Java中使用雙重檢查鎖實現(xiàn)單例:http://www.importnew.com/12196.html
- 雙重檢查鎖定與延遲初始化
相關文章
Java中構造器內(nèi)部的多態(tài)方法的行為實例分析
這篇文章主要介紹了Java中構造器內(nèi)部的多態(tài)方法的行為,結合實例形式分析了java構造器內(nèi)部多態(tài)方法相關原理、功能及操作技巧,需要的朋友可以參考下2019-10-10
SpringBoot使用@EnableAutoConfiguration實現(xiàn)自動配置詳解
你有想過SpringBoot為什么能夠自動的幫我們創(chuàng)建一個Bean對象么?或許在我們使用的時候只需要在自己自定義的配置文件中加入@Bean對象就可以,但SpringBoot是如何來創(chuàng)建的呢2022-08-08

