1. 程式人生 > >關於並發場景下,通過雙重檢查鎖實現延遲初始化的優化問題隱患的記錄

關於並發場景下,通過雙重檢查鎖實現延遲初始化的優化問題隱患的記錄

ron href 修飾符 屬性 tin 記錄 targe turn 優化問題

  首先,這個問題是從《阿裏巴巴Java開發手冊》的1.6.12(P31)上面看到的,裏面有這樣一句話,並列出一種反例代碼(以下為仿寫,並非與書上一致):

  在並發場景下,通過雙重檢查鎖(double-checked locking)實現延遲初始化的優化問題隱患,推薦解決方案中較為簡單的一種(適用於JDK5及以上的版本),即目標屬性聲明為volatile型。

  

 1 public class Singleton {
 2     private static Singleton instance=null;
 3     private Singleton() {
 4     }
5 6 public static Singleton getInstance() { 7 if (instance == null) {//1 8 synchronized (Singleton.class) {//2 9 if (instance == null) {//3 10 instance = new Singleton();//4 11 } 12 } 13 } 14 return instance;
15 } 16 }

  這樣的單例模式經常容易看到(我之前也是這樣寫的),這樣的問題在於初始化代碼:

     instance = new Singleton();

JVM會將這段代碼分成三步去執行:

  a.分配內存空間;

  b.構造Singleton;

  c.將instance指向構造的實例。

  如果執行的過程是a->b->c的話,那上面的代碼是沒有問題的,但是有時JVM會基於指令優化的目的將指令重排,導致指令執行流程變為a->c->b。這樣當線程A執行到4開始初始化單例對象的c流程時,線程B執行到1處,由於instance對象已經將內部指針指向分配的內存空間(即不為null),會直接返回未完全構造好的實例,從而出錯。按照《手冊》的說法,修改後的代碼如下

 1 public class Singleton {
 2     private static volatile Singleton instance=null;    //添加volatile修飾符
 3     private Singleton() {
 4     }
 5 
 6     public static Singleton getInstance() {
 7         if (instance == null) {//1
 8             synchronized (Singleton.class) {//2
 9                 if (instance == null) {//3
10                     instance = new Singleton();//4
11                 }
12             }
13         }
14         return instance;
15     }
16 }

  由於volatile自帶的“禁止指令重優化”語義,初始化語句只能按照a->b->c的順序進行執行。詳細的解釋可以參考這篇文章:《Java單例模式中雙重檢查鎖的問題》

  

註:盡管這個問題看起來很簡單,但是我在本地沒有辦法重演這個bug,這個bug出現的關鍵時刻在於線程A在執行a->c->b鏈的c時,線程B將構造完的instance返回並使用才會出錯,但是一般的場景下是沒有辦法在這麽短的時間間隔內捕獲到這個間隔的。不過出於保險的目的,單例模式的我還是加上volatile修飾符比較好。

  

關於並發場景下,通過雙重檢查鎖實現延遲初始化的優化問題隱患的記錄