介紹
當我們有一定的java基礎的時候會覺得創建對象不就是使用new關鍵字創建一個對象嘛。還能有什么步驟?
其實不然JVM的機制問題創建步驟其實包含了三步:
- 分配內存空間
- 執行構造器來初始化對象
- 將創建的對象指向內存空間
但是,JVM有時為了性能的問題會進行指令重排,雖然平時使用的時候沒有什么問題,但是在多線程的情況下可能會出現問題
問題
比如我們的單例模式中的懶漢式:
//DCL懶漢式(雙重檢測懶漢式)
public class SingletonDemo03 {
//私有化類構造器
private SingletonDemo03(){
}
//2.類初始化的時候,並不立即加載該對象
private static SingletonDemo03 instance;
//3.提供獲取該對象的公開方法
public static SingletonDemo03 getInstance(){
if(instance == null){
synchronized (SingletonDemo03.class){//鎖住這個類,進來判斷這次調用是否是第一次,如果是就創建,如果不是直接跳過
if (instance == null){
instance = new SingletonDemo03();
}
}
}
return instance;
}
}
這樣看,這個單例模式沒有問題,進入getInstance()方法,先判斷是否創建了該實例,如果創建了直接返回,沒有創建進行加鎖再判斷最后再初始化。這樣不是直接避免了多線程嗎?
按照上面創建對象的邏輯,其實是有問題的,如果沒有進行指令重排(1->2->3)當然不會有問題,但是如果進行了指令重排,按照1->3->2進行呢?
- 第一個線程進來判斷沒有初始化加鎖再判斷還沒有那么創建對象當創建對象的步驟進行到1->2的時候CPU調度執行第二個線程進來了
- 第二個線程進來,發現instance這個對象已經指向了一個分配的內存空間(但是沒有執行構造器初始化因為第一個線程還沒有執行創建對象的第三步),ok,直接返回使用,然后使用就報null指針異常。因為這個對象還沒有初始化。
如何解決呢?
出現這個問題的原因就是JVM進行了指令重排,我們可以使用volatile關鍵字,對instance進行修飾,這樣就可以防止指令重排了:
//DCL懶漢式(雙重檢測懶漢式)
public class SingletonDemo03 {
//私有化類構造器
private SingletonDemo03(){
}
//2.類初始化的時候,並不立即加載該對象
private volatile static SingletonDemo03 instance;
//3.提供獲取該對象的公開方法
public static SingletonDemo03 getInstance(){
if(instance == null){
synchronized (SingletonDemo03.class){//鎖住這個類,進來判斷這次調用是否是第一次,如果是就創建,如果不是直接跳過
if (instance == null){
instance = new SingletonDemo03();
}
}
}
return instance;
}
}