傳統單例模式雙重檢查鎖存在的問題


單例模式1.0:

public class Singleton {
	private static Singleton sInstance;

	public static Singleton getInstance() {

		if (sInstance == null) {  // 1
			sInstance = new Singleton();
		}
		return sInstance;
	}

	private Singleton() {
	}
}

  這種方式很辣雞,因為多線程環境下不能保證單例。

單例模式2.0:

public class Singleton {
	private static volatile Singleton sInstance;

	public static synchronized Singleton getInstance() {

		if (sInstance == null) {
			sInstance = new Singleton();
		}
		
		return sInstance;
	}

	private Singleton() {
	}
}

  這種方式也很辣雞,因為多線程環境下每個線程執行getInstance()都要阻塞,效率很低。

單例模式3.0:

public class Singleton {
	private static Singleton sInstance;
	public static Singleton getInstance() {
		if (sInstance == null) {  // 位置1
			synchronized (Singleton.class) {
				if (sInstance == null) {
					sInstance = new Singleton();  // 位置2
				}
			}
		}
		return sInstance;
	}
	private Singleton() {}
}

  這種方式使用雙重檢查鎖,多線程環境下執行getInstance()時先判斷單例對象是否已經初始化,如果已經初始化,就直接返回單例對象,如果未初始化,就在同步代碼塊中先進行初始化,然后返回,效率很高。

  但是這種方式是一個錯誤的優化,問題的根源出在位置2

  sInstance =new Singleton();這句話創建了一個對象,他可以分解成為如下3行代碼:

memory = allocate();  // 1.分配對象的內存空間
ctorInstance(memory);  // 2.初始化對象
sInstance = memory;  // 3.設置sInstance指向剛分配的內存地址

  上述偽代碼中的2和3之間可能會發生重排序,重排序后的執行順序如下

memory = allocate();  // 1.分配對象的內存空間
sInstance = memory;  // 2.設置sInstance指向剛分配的內存地址,此時對象還沒有被初始化
ctorInstance(memory);  // 3.初始化對象

  因為這種重排序並不影響Java規范中的規范:intra-thread sematics允許那些在單線程內不會改變單線程程序執行結果的重排序。

  但是多線程並發時可能會出現以下情況

  線程B訪問到的是一個還未初始化的對象。

解決方案1:

public class Singleton {
private static volatile Singleton sInstance;
public static Singleton getInstance() {
        if (sInstance == null) {
                synchronized (Singleton.class) {
                        if (sInstance == null) {
                           sInstance = new Singleton();
                            }
                        }
                    }
                    return sInstance;
            }
private Singleton() {}
}

  將對象聲明為volatitle后,前面的重排序在多線程環境中將會被禁止

解決方案2:

public class Singleton {
	private Singleton(){};
	private static class Inner{
		private static Singleton SINGLETION=new Singleton();
	}
	public static Singleton getInstance(){
		return Inner.SINGLETION;
	}
}   

 

  靜態內部類不會隨着外部類的初始化而初始化,他是要單獨去加載和初始化的,當第一次執行getInstance方法時,Inner類會被初始化。

  靜態對象SINGLETION的初始化在Inner類初始化階段進行,類初始化階段即虛擬機執行類構造器<clinit>()方法的過程。

  虛擬機會保證一個類的<clinit>()方法在多線程環境下被正確的加鎖和同步,如果多個線程同時初始化一個類,只會有一個線程執行這個類的<clinit>()方法,其它線程都會阻塞等待。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM