單例模式之懶漢單例(延遲初始化)多執行緒再解析
阿新 • • 發佈:2018-11-01
單例模式之懶漢單例(延遲初始化)多執行緒再解析
1、多執行緒下的懶漢單例:
列印結果說明創建出來三個物件,並不是單例,多執行緒下懶漢單例是非執行緒安全的。
多執行緒下單例模式非執行緒安全的解決方案:
1、宣告synchronized關鍵字,實現同步方法
加入同步方法得到相同例項物件,此方法執行效率非常低,是同步執行,下一個執行緒想要取得物件,必須等上一個執行緒釋放鎖後,才能執行。
2、使用同步程式碼塊
與使用synchronized同步方法一樣是同步執行,效率非常低。得到相同例項物件。
3、部分程式碼上鎖,進行單獨同步,非執行緒安全
4、使用DCL雙重檢查鎖定
使用DCL雙重檢查鎖定成功解決懶漢模式的多執行緒問題,DCL也是大多數多執行緒結合單例模式使用的解決方案。
DCL是常見的延遲初始化技術,但有一個錯誤的用法。使用DCL需要一些技巧。
存在錯誤的根源:
a.多執行緒試圖在同一時間建立物件,會通過加鎖來保證只有一個執行緒建立物件。
b.在物件建立好後,執行getInstance()方法將不需要獲取鎖,直接返回建立好的物件。
可以分解為:
memory=allocate();1.分配物件的記憶體空間
ctorInstance(memory);2.初始化物件
instance=memory;3.設定instance指向剛分配的記憶體地址
在Java記憶體模型中為了優化程式碼會重排程式碼,會導致執行緒看到一個還沒被初始化的物件。
執行緒安全的延遲初始化方案:
1、基於volatile的解決
宣告volatile,初始化程式碼重排就會被禁止,此方案是通過禁止程式碼重排來實現執行緒安全的延遲載入。
建立物件的過程,例項化物件一般分為三個過程。
1、分配記憶體空間。
2 、初始化物件。
3 、將記憶體空間地址賦值給物件的引用。
但是由於重排序的緣故,步驟2、3可能會發生重排序,其過程如下
1、分配記憶體空間
2、將記憶體空間的地址賦值給對應的引用
3、初始化物件
如果不加volatile的話,可能執行緒1在初始化的時候重排序了,執行緒2看到singleton != null,已經返回singleton,其實執行緒1還沒有完成初始化,僅僅只不過是分配了記憶體空間而已!
除了使用DCL解決多執行緒單例模式的非執行緒安全的問題,使用靜態內建類也可以實現同樣的效果。
每天努力一點,每天都在進步。
1、多執行緒下的懶漢單例:
public class Lazysingleton { private static Lazysingleton m_instance = null; // 私有預設構造方法,外界無法直接例項化 private Lazysingleton() { } // 靜態工廠方法 public static Lazysingleton getInstance() throws InterruptedException { // 延遲載入 if (m_instance == null) { // 模擬建立物件的準備工作 Thread.sleep(3000); m_instance = new Lazysingleton();// 初始化這個單例 } return m_instance; } }
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println(Lazysingleton.getInstance().hashCode());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class TestLazy1 { public static void main(String[] args) { MyThread t1=new MyThread(); MyThread t2=new MyThread(); MyThread t3=new MyThread(); t1.start(); t2.start(); t3.start(); } }
列印結果說明創建出來三個物件,並不是單例,多執行緒下懶漢單例是非執行緒安全的。
多執行緒下單例模式非執行緒安全的解決方案:
1、宣告synchronized關鍵字,實現同步方法
加入同步方法得到相同例項物件,此方法執行效率非常低,是同步執行,下一個執行緒想要取得物件,必須等上一個執行緒釋放鎖後,才能執行。
2、使用同步程式碼塊
與使用synchronized同步方法一樣是同步執行,效率非常低。得到相同例項物件。
3、部分程式碼上鎖,進行單獨同步,非執行緒安全
4、使用DCL雙重檢查鎖定
使用DCL雙重檢查鎖定成功解決懶漢模式的多執行緒問題,DCL也是大多數多執行緒結合單例模式使用的解決方案。
DCL是常見的延遲初始化技術,但有一個錯誤的用法。使用DCL需要一些技巧。
存在錯誤的根源:
a.多執行緒試圖在同一時間建立物件,會通過加鎖來保證只有一個執行緒建立物件。
b.在物件建立好後,執行getInstance()方法將不需要獲取鎖,直接返回建立好的物件。
問題:當代碼讀取到m_instance不為空,m_instance引用的物件有可能還沒有完成初始化。就會出出現問題。
m_instance = new Lazysingleton();可以分解為:
memory=allocate();1.分配物件的記憶體空間
ctorInstance(memory);2.初始化物件
instance=memory;3.設定instance指向剛分配的記憶體地址
在Java記憶體模型中為了優化程式碼會重排程式碼,會導致執行緒看到一個還沒被初始化的物件。
執行緒安全的延遲初始化方案:
1、基於volatile的解決
宣告volatile,初始化程式碼重排就會被禁止,此方案是通過禁止程式碼重排來實現執行緒安全的延遲載入。
建立物件的過程,例項化物件一般分為三個過程。
1、分配記憶體空間。
2 、初始化物件。
3 、將記憶體空間地址賦值給物件的引用。
但是由於重排序的緣故,步驟2、3可能會發生重排序,其過程如下
1、分配記憶體空間
2、將記憶體空間的地址賦值給對應的引用
3、初始化物件
如果不加volatile的話,可能執行緒1在初始化的時候重排序了,執行緒2看到singleton != null,已經返回singleton,其實執行緒1還沒有完成初始化,僅僅只不過是分配了記憶體空間而已!
除了使用DCL解決多執行緒單例模式的非執行緒安全的問題,使用靜態內建類也可以實現同樣的效果。
每天努力一點,每天都在進步。