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

單例模式1.0:java

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:spa

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行代碼:blog

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>()方法,其它線程都會阻塞等待。

相關文章
相關標籤/搜索