爲何雙重檢查鎖模式須要 volatile ?

雙重檢查鎖定(Double check locked)模式常常會出如今一些框架源碼中,目的是爲了延遲初始化變量。這個模式還能夠用來建立單例。下面來看一個 Spring 中雙重檢查鎖定的例子。java

DCL.png

這個例子中須要將配置文件加載到 handlerMappings中,因爲讀取資源比較耗時,因此將動做放到真正須要 handlerMappings 的時候。咱們能夠看到 handlerMappings 前面使用了volatile 。有沒有想過爲何必定須要 volatile?雖然以前瞭解了雙重檢查鎖定模式的原理,可是卻忽略變量使用了 volatile安全

下面咱們就來看下這背後的緣由。多線程

錯誤的延遲初始化例子

想到延遲初始化一個變量,最簡單的例子就是取出變量進行判斷。app

errorexample.png

這個例子在單線程環境交易正常運行,可是在多線程環境就有可能會拋出空指針異常。爲了防止這種狀況,咱們須要使用 synchronized 。這樣該方法在多線程環境就是安全的,可是這麼作就會致使每次調用該方法獲取與釋放鎖,開銷很大。框架

深刻分析能夠得知只有在初始化的變量的須要真正加鎖,一旦初始化以後,直接返回對象便可。性能

因此咱們能夠將該方法改造如下的樣子。優化

DCLerror.png

這個方法首先判斷變量是否被初始化,沒有被初始化,再去獲取鎖。獲取鎖以後,再次判斷變量是否被初始化。第二次判斷目的在於有可能其餘線程獲取過鎖,已經初始化改變量。第二次檢查還未經過,纔會真正初始化變量。spa

這個方法檢查斷定兩次,並使用鎖,因此形象稱爲雙重檢查鎖定模式。線程

這個方案縮小鎖的範圍,減小鎖的開銷,看起來很完美。然而這個方案有一些問題卻很容易被忽略。3d

new 實例背後的指令

這個被忽略的問題在於 Cache cache=new Cache() 這行代碼並非一個原子指令。使用 javap -c指令,能夠快速查看字節碼。

// 建立 Cache 對象實例,分配內存
       0: new           #5                  // class com/query/Cache
       // 複製棧頂地址,並再將其壓入棧頂
       3: dup
	// 調用構造器方法,初始化 Cache 對象
       4: invokespecial #6                  // Method "<init>":()V
	// 存入局部方法變量表
       7: astore_1
複製代碼

從字節碼能夠看到建立一個對象實例,能夠分爲三步:

  1. 分配對象內存
  2. 調用構造器方法,執行初始化
  3. 將對象引用賦值給變量。

虛擬機實際運行時,以上指令可能發生重排序。以上代碼 2,3 可能發生重排序,可是並不會重排序 1 的順序。也就是說 1 這個指令都須要先執行,由於 2,3 指令須要依託 1 指令執行結果。

Java 語言規規定了線程執行程序時須要遵照 intra-thread semantics。**intra-thread semantics ** 保證重排序不會改變單線程內的程序執行結果。這個重排序在沒有改變單線程程序的執行結果的前提下,能夠提升程序的執行性能。

雖然重排序並不影響單線程內的執行結果,可是在多線程的環境就帶來一些問題。

image.png

上面錯誤雙重檢查鎖定的示例代碼中,若是線程 1 獲取到鎖進入建立對象實例,這個時候發生了指令重排序。當線程1 執行到 t3 時刻,線程 2 恰好進入,因爲此時對象已經不爲 Null,因此線程 2 能夠自由訪問該對象。而後該對象還未初始化,因此線程 2 訪問時將會發生異常。

volatile 做用

正確的雙重檢查鎖定模式須要須要使用 volatilevolatile主要包含兩個功能。

  1. 保證可見性。使用 volatile 定義的變量,將會保證對全部線程的可見性。
  2. 禁止指令重排序優化。

因爲 volatile 禁止對象建立時指令之間重排序,因此其餘線程不會訪問到一個未初始化的對象,從而保證安全性。

注意,volatile禁止指令重排序在 JDK 5 以後才被修復

使用局部變量優化性能

從新查看 Spring 中雙重檢查鎖定代碼。

DCL.png

能夠看到方法內部使用局部變量,首先將實例變量值賦值給該局部變量,而後再進行判斷。最後內容先寫入局部變量,而後再將局部變量賦值給實例變量。

使用局部變量相對於不使用局部變量,能夠提升性能。主要是因爲 volatile 變量建立對象時須要禁止指令重排序,這就須要一些額外的操做。

總結

對象的建立可能發生指令的重排序,使用 volatile 能夠禁止指令的重排序,保證多線程環境內的系統安全。

幫助文檔

雙重檢查鎖定與延遲初始化
有關「雙重檢查鎖定失效」的說明

其餘平臺.png
相關文章
相關標籤/搜索