單例模式中有一種實現方式叫雙重檢查鎖,主要是爲了更好、更安全的實現單例功能。先來看一下該方法的核心代碼:
- <span style="font-size:18px;">public class DoubleCheckedLocking{
- private static Instance instance;
- public static Instance getInstance(){
- if(instance ==null){
- synchronized (DoubleCheckedLocking.class){
- if(instance ==null)
- instance=new Instance(); //①
- }
- }
- return instance;
- }
- }</span>
問題源頭:
上述代碼標號爲①的代碼功能是是創建實例對象,可以分解爲如下僞代碼步驟:
- <span style="font-size:18px;">memory = allocate() ; //分配對象的內存空間
- ctorInstance(memory); //②初始化對象
- instance=memory; //③設置instance指向剛分配的內存地址</span>
其中②和③之間,在某些編譯器編譯時,可能出現重排序(主要是爲了代碼優化),此時的代碼如下:
- <span style="font-size:18px;">memory = allocate() ; //分配對象的內存空間
- instance=memory; //③設置instance指向剛分配的內存地址
- ctorInstance(memory); //②初始化對象
- </span>
多線程下執行時序圖:
由於單線程中遵守intra-thread semantics,從而能保證即使②和③交換順序後其最終結果不變。但是當在多線程情況下,線程B將看到一個還沒有被初始化的對象,此時將會出現問題。
解決方案:
1、不允許②和③進行重排序
2、允許②和③進行重排序,但排序之後,不允許其他線程看到。
基於volatile的解決方案
對前面的雙重鎖實現的延遲初始化方案進行如下修改:
- <span style="font-size:18px;">public class DoubleCheckedLocking{</span>
- <span style="font-size:18px;"> private volatile static Instance instance;
- public static Instance getInstance(){
- if(instance ==null){
- synchronized (DoubleCheckedLocking.class){
- if(instance ==null)
- instance=new Instance(); //用volatile修飾,不會再出現重排序
- }
- }
- return instance;
- }
- }</span>
注意:這個解決方案需要JDK5或更高版本(因爲從JDK5開始使用新的JSR-133內存模型規範,這個規範增強了volatile的語義)
基於類初始化的解決方案
JVM在類的初始化階段(即在Class被加載後,且被線程使用之前),會執行類的初始化。在執行類的初始化期間,JVM會去獲取一個鎖。這個鎖可以同步多個線程對同一個類的初始化。基於這個特性,可以實現另一種線程安全的延遲初始化方案。
- <span style="font-size:18px;">public class InstanceFactory {
- private static class InstanceHolder {
- public static Instance instance = new Instance();
- }
- public static Instance getInstance() {
- return InstanceHolder.instance ; //這裏將導致InstanceHolder類被初始化
- }
- }</span>
該方案的實質是,允許②和③進行重排序,但不允許非構造線程(此處是B線程)“看到”這個重排序。
原鏈接:點擊