單例模式的雙重檢查
雙重檢查
public class Singletone{
private static Instance instance;
public Instance getInstance(){
if(instance == null){
synchronized(Singletone.class){
if(instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
問題:
instance = new Instance();
是由三個步驟組成的:
- 為對象分配內存
- 實例化對象
- 將引用指向對應的內存地址
但是第2,3步可能發生指令重排列,導致先將引用指向一個未實例化對象的內存地址,然后再進行實例化對象。
若此時第二個線程進行第一個非空判斷時,則為false,會直接返回還沒有實例化對象的內存地址,從而可能產生異常。
解決:
- 禁止指令重排列
- 允許指令重排列,但是其他線程“不可見”
方案一:基於volatile
禁止指令重排列
public class Singletone{
private volatile static Instance instance;
public Instance getInstance(){
if(instance == null){
synchronized(Singletone.class){
if(instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
在成員變量中加入volatile
變量,禁止使用new創建對象時的指令重排列。
方案二:基於類初始化的解決方案
public class Singletone{
private static class InstanceHolder{
public static Instance instance = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.instance;
}
}
JVM在類初始化階段進行類的初始化。在初始化期間,JVM會獲取一個鎖,從而同步多個線程對同一個類的初始化。
第一個獲得鎖的線程會完成實例對象的創建,其他線程會直接獲取創建的實例對象。
參考: