java多執行緒之延遲初始化
有時候我們可能推遲一些高開銷的物件的初始化操作,並且只有在使用這些物件時才進行初始化,開發者可以採用延遲初始化來實現該需求。但是要正確實現執行緒安全的延遲初始化還是需要一些技巧的,否則很容易出現問題。下面是一個非執行緒安全的延遲初始化的例子:
public class UnsafeLazyInit {
private static Instance instance;
public static Instance getInstance() {
if(instance == null) { //1:A執行緒執行
instance = new Instance(); //2:B執行緒執行
}
return instance;
}
}
在UnsafeLazyInit類中,假設A執行緒執行程式碼1的同時,B執行緒執行程式碼2,此時執行緒A可能會看到instance物件還沒有完成初始化。
對於UnsafeLazyInit類,我們可以對getInstance方法做同步處理來實現執行緒安全的延遲初始化,程式碼如下:
public class UnsafeLazyInit {
private static Instance instance;
public synchronized static Instance getInstance() {
if(instance == null) { //1:A執行緒執行
instance = new Instance(); //2:B執行緒執行
}
return instance;
}
}
由於對getInstance方法做了同步處理,將導致效能開銷,如果getInstance方法被多個執行緒頻繁呼叫的話,將會導致程式執行效能的下降,而如果getInstance不會被多個執行緒頻繁呼叫,那麼這個方案將會提供令人滿意的效能。
對於synchronized方法可能帶來的程式執行效能的下降,我們可以使用一種“聰明”的技巧:雙重檢查鎖定(Double-Checked Locking)來降低同步的開銷。下面是使用雙重檢查鎖來實現延遲初始化的示例程式碼:
public class DoubleCheckedLocking {
private static Instance instance;
public static Instance getInstance() {
if(instance == null) { //1:第一次檢查
synchronized(DoubleCheckedLocking.class) { //2:加鎖
if(instance == null) { //3:第二次檢查
instance = new Instance(); //4:問題的根源處在這裡
}
}
}
return instance;
}
}
按照上面的程式碼,如果第一次檢查instance不為null,則不需要執行下面的加鎖和二次檢查與初始化操作,因此可以大大降低synchronized帶來的效能開銷,似乎是兩全其美的實現方式。
雙重檢查鎖定看起來似乎很完美,但這是一個錯誤的優化!線上程執行1:第一次檢查時,程式碼讀取到instance不為null,其實instance有肯能還沒有完成初始化,該問題的根源就在於:重排序。
在建立instance例項時,instance = new Instance()這行程式碼可以分解為如下3行虛擬碼:
memory = allocate(); //1:分配物件的記憶體空間
ctorInstance(memory); //2: 初始化物件
instance = memory; //3: 設定instance指向剛分配的記憶體
上述虛擬碼中的2和3之間,可能會發生重排序,重排序後的執行順序如下:
memory = allocate(); //1:分配物件的記憶體空間
instance = memory; //3: 設定instance指向剛分配的記憶體 注意:此時物件還沒有被初始化!
ctorInstance(memory); //2: 初始化物件
在上邊的java程式碼中,如果instance = new Instance()發生了重排序,另一個併發執行緒B就有可能在第一次檢查時instance不為null,執行緒B接下來將訪問instance所引用的物件,但此時該物件可能還沒有被A執行緒初始化,也就是會訪問一個未被初始化的物件。
知道了這個問題根源以後,可以有兩個辦法來實現執行緒安全的延遲初始化:
1.不允許2和3重排序。
2.允許2和3重排序,但不允許其它執行緒“看到”這個重排序。
1.不允許2和3重排序,只需對雙重檢查鎖定做小小的修改即可,我們把instance宣告為volatile型,就可以實現執行緒安全的延遲初始化,示例程式碼如下:
public class DoubleCheckedLocking {
private volatile static Instance instance;
public static Instance getInstance() {
if(instance == null) {
synchronized(DoubleCheckedLocking.class) {
if(instance == null) {
instance = new Instance(); //instance為volatile,現在沒有問題了。
}
}
}
return instance;
}
}
當物件宣告為volatile後,虛擬碼中的2和3的重排序,在多執行緒環境中將被禁止。
2.允許2和3重排序,但不允許其它執行緒“看到”這個重排序。
JVM在類的初始化階段(即在Class被載入後,且被執行緒使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖,這個鎖可以同步多個執行緒對一個類的初始化。基於這個特性,我們可以在允許2和3重排序的情況下,實現執行緒安全的延遲初始化。
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance; //這裡將導致InstanceHolder類被初始化
}
}
這個方案的實質是:允許2和3重排序,但是不允許非構造執行緒(如執行緒B)“看到”這個重排序。
在InstanceFactory中,首次執行getInstance方法的執行緒(如執行緒A)將導致InstanceHolder類被初始化,但是如果多個執行緒同時呼叫getInstance方法,將會怎樣呢?
Java語言規範規定,對於每一個類或介面C,都有一個唯一的初始化鎖LC與之對應,從C到LC的對映,由JVM的具體實現去自由實現。JVM在初始化期間會獲取這個初始化鎖,並且每個執行緒至少獲取一次鎖來確保這個類被初始化了。
這個過程比較冗長,這裡不做過多描述,總之就是JVM通過初始化鎖同步了多個執行緒同時初始化一個物件的操作,保證類不會被多次初始化。
通過對比基於volatile的雙重檢查鎖定的方案和基於類初始化的方案,我們發現基於類初始化的方案更加簡潔。但基於volatile的雙重檢查鎖定方案有一個額外優勢:除了可以對靜態欄位實現延遲初始化外,還可以對例項欄位實現延遲初始化。
在設計模式中,有一個單例模式(Singleton),該模式比較常用,我們可以使用基於volatile的雙重檢查鎖定和基於類初始化的方案去建立單例物件,在實際工作中,我一般是使用基於類初始化的方案去實現單例模式。
相關文章
- Java多執行緒之執行緒中止Java執行緒
- Java多執行緒之CASJava執行緒
- Java多執行緒之FutureTaskJava執行緒
- java多執行緒之(synchronized)Java執行緒synchronized
- java多執行緒之執行緒的基本使用Java執行緒
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- Java多執行緒之守護執行緒實戰Java執行緒
- java多執行緒之Thread類Java執行緒thread
- java多執行緒之volatile理解Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- 【Java多執行緒】輕鬆搞定Java多執行緒(二)Java執行緒
- 一步步瞭解執行緒池之定時延遲執行-ScheduledThreadPool執行緒thread
- Java多執行緒之進階篇Java執行緒
- Java多執行緒之synchronized詳解Java執行緒synchronized
- Java多執行緒之synchronized理論Java執行緒synchronized
- Java多執行緒之Callable,Future,FutureTaskJava執行緒
- java多執行緒Java執行緒
- Java - 多執行緒Java執行緒
- java——多執行緒Java執行緒
- java 多執行緒Java執行緒
- Java多執行緒-執行緒狀態Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- java多執行緒9:執行緒池Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- 【java多執行緒】(二)執行緒停止Java執行緒
- 多執行緒系列之 執行緒安全執行緒
- iOS 多執行緒之執行緒安全iOS執行緒
- Android多執行緒之執行緒池Android執行緒
- Java多執行緒學習(一)Java多執行緒入門Java執行緒
- Java多執行緒(一)多執行緒入門篇Java執行緒
- 手撕Java多執行緒(四)執行緒之間的協作Java執行緒
- java 多執行緒之使用 interrupt 停止執行緒的幾種方法Java執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- Java 多執行緒共享模型之管程(上)Java執行緒模型
- Java多執行緒與併發之ThreadLocalJava執行緒thread
- Java多執行緒之記憶體模型Java執行緒記憶體模型