面試題剖析:單例設計模式執行緒安全問題
本文作者:黃海燕,叩丁狼高階講師。原創文章,轉載請註明出處。
1. volatile 關鍵字
1.1 volatile 關鍵字作用:
在百度百科擷取的描述如下:
叩丁狼教育.png
說明volatile 關鍵字作用作用有兩點:
-
防止指令重排:規定了volatile 變數不能指令重排,必須先寫再讀。
-
記憶體可見:執行緒從記憶體中讀取volatile修飾的變數的資料,直接從主記憶體中獲取資料,不需要經過CPU快取,這樣使得多執行緒獲取的資料都是一致的。如圖所示:
叩丁狼教育.png
1.2 volatile和synchronized的區別
volatile不能夠替代synchronized,原因有兩點:
1.對於多執行緒,不是一種互斥關係
2.不能保證變數狀態的“原子性操作”,所以volatile不能保證原子性問題
1.3解決單例設計模式執行緒安全問題
實現單例設計模式兩種
- 餓漢式(不存在原子性,是執行緒安全的)
實現1:
//餓漢式:很餓需要立馬建立物件 public class Singleton1 { //1.定義一個物件 private static final Singleton1 instance = new Singleton1(); //2.私有化構造器,避免外部類建立物件 private Singleton1(){} //3.獲取物件的靜態方法 public static Singleton1 getInstance(){ return instance; } }
實現2:列舉方式(最安全)
//餓漢式(列舉)
public enum EnumSingleton {
INSTANCE;
}
- 懶漢式(懶載入):存在原子性問題,執行緒不安全
//懶漢式:很懶,使用物件的時候才建立物件,但是省資源 public class Singleton2 { private static Singleton2 instance; private Singleton2() { } public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
①由於 instance = new Singleton2();存在原子性問題,所以我們應該用synchronized程式碼塊將其同步。這裡由於synchronized很耗資源,所以粒度越小越好,最好不要使用同步方法。
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
instance = new Singleton2();
}
}
return instance;
}
②在多個執行緒的情況,可能存線上程1和執行緒2都已經執行了instance == null的判斷,可能執行緒1搶到了鎖執行緒2就阻塞在了同步程式碼塊入口,當執行緒1執行完畢釋放鎖,執行緒2拿到鎖的時候因為之前判斷instance == null為true就會建立物件,那麼此時就無法保證單例了,所以我們應該繼續在同步程式碼塊中再判斷一次instance == null。這樣的做法我們有個專業名詞,稱之為雙重檢查鎖定。
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class){
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
③instance = new Singleton2();這句程式碼存在指令重排問題,什麼意思?
一般的執行順序為:
1)給物件分配記憶體空間
2)初始化物件
3)變數instance 指向記憶體空間
在單執行緒中,由於步驟2)和步驟3)即使交換順序也不會影響最終效果,所以可能發生指令重排,順序為:
1)給物件分配記憶體空間
3)變數instance 指向記憶體空間
2)初始化物件
如果出現指令重排就會發生以下問題,如圖所示:
叩丁狼教育.png
注意:由於執行緒2在外面的判斷就為false,沒有去執行需要競爭鎖的程式碼,所以沒有進入阻塞狀態,和執行緒1是並行狀態,導致訪問物件出現問題,所以為了避免這個問題,我們應該不讓指令重排發生,那麼使用volatile修飾物件,讓物件先寫再讀,固定物件的指令,避免指令重排。
最終執行緒安全的單例懶漢式程式碼如下:
public class Singleton2 {
private static volatile Singleton2 instance;
private Singleton2() {}
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class){
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
想獲取更多技術視訊,請前往叩丁狼官網:http://www.wolfcode.cn/all_article.html