1. 程式人生 > >傳統單例模式雙重檢查鎖存在的問題

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

單例模式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>()方法,其它執行緒都會阻塞等待。