Java中的volatile在使用雙層檢查實現單例模式的解讀


 

1.前言

因為今天在想到這個問題的時候腦子不是很清楚,就想查一下網上的資料,結果發現一個個寫的囫圇吞棗。后來突然想起來了,於是打算記錄下來。

注意此種方法只針對JDK1.5及以上,之前好像是volatile的關鍵字設計有問題?

2.雙層檢查實現單例模式的由來

最開始只有一層檢查,

【失敗的設計】
public  Resource getResource() {
  if  (resource ==  null ) { 
     resource =  new  Resource(); 
  }
  return  resource;
}
大家應該都可以看出,在多線程情況下,會出現多個new Resource()的動作
 
這時,有人就提出了一個解決方案
【成功但是糟糕的設計】
public synchronized  Resource getResource() {
  if  (resource ==  null ) { 
     resource =  new  Resource(); 
  }
  return  resource;
}
可以看到,他給getResource方法加上了synchronized關鍵字,這足以保證只有一個線程會執行new Resource操作。
這個設計可以滿足需求,但是有一個不太好的地方就是每次執行getResource方法的時候都會有加鎖去鎖的開銷,而這中加鎖只有在初始化前才是必須的。
 
所以有有人說,那可不可以這樣: 1
public  Resource getResource() {
  if (resource == null ) { 
   synchronized ( this ){ 
    if (resource== null ) {
     resource = new Resource(); 
    }  
  
  }
  return resource;
}
稍作解釋,他的想法是進行兩次判斷,如果第一次判斷不為null,也就是resource已經初始化,那么久不會進去執行加鎖去鎖的操作了。
但這個方案可不可能需要看resource有沒有加volatile關鍵字。

如果加了volatile關鍵字,這個方法是有效的。如果沒有,這個方法是有一定多線程風險的。

現在進入本文的重點,此處為什么不加volatile有風險。

首先聲明:

此處利用了volatile的一個關鍵字特性:防止局部指令重排序

簡單的概括就是,

volatile禁止指令重排序的一些規則:2
  1.當第二個操作是voaltile寫時,無論第一個操作是什么,都不能進行重排序
  2.當第一個操作是volatile讀時,不管第二個操作是什么,都不能進行重排序
  3.當第一個操作是volatile寫時,第二個操作是volatile讀時,不能進行重排序

下面來看new Resource()這個操作,

它可以分解成三個操作,

【偽代碼】3

1.memory = allocate()

2.createInstance(memory)

3.resource = memory

其中1,2指令有數據依賴,所以不會被重排序。而2,3指令沒有數據依賴,如果沒有volatile關鍵字,可能會被重排序。

那么假如2,3執行順序進行了調換。

那么就有可能發生,假設A,B線程都即將執行getResource操作,目前在A線程,

首先A線程第一次判斷resource是否為null,結果為null,那么加鎖進入執行創建對象的這三步。

假設執行了上述的1,3后,發生了調度,B開始執行。

此時它面臨的狀態是,判斷resource是否為null,結果不為null.因為剛才A執行了3已經給resource賦值了。

那么B會認為resource已經初始化完成,它可能要對這個對象進行一些操作,但是事實上A還沒有執行2操作,resource對象還沒有初始化完成。

這樣運行下去可能會產生異常,風險由此產生。

不過加了volatile后,在執行對resource寫之前,volatile關鍵字保證了1,2,3操作不會重排序(需要考證),這樣就保證了resource要么為null,要么就是一個完整的resource對象。

順便補充一下,加了volatile之后,即使在執行1,2,3的時候發生了調度,因為鎖在A線程手里,所以B線程沒有辦法拿到鎖進行初始化。所以即使調度時候resource為null,也不會發生多次初始化的情形。

 

如果需要對volatile有更多的了解,

可以訪問官方Java Language Specification

關於volatile: http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#d5e12277

關於Java內存模型: http://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4

參考文檔:

1.http://www.jb51.net/article/80201.htm

2.https://blog.csdn.net/hqq2023623/article/details/51013468

3.http://blog.csdn.net/xiakepan/article/details/52444565


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM