天天看點

單例模式與double check

本文主要是講double check,通常double check比較少用,一般是在高并發的情況下。但是建議大家寫單例的時候都用上。

單例模式分兩種:餓漢式與懶漢式,餓漢式是指在類加載時就進行執行個體化,而懶漢式是指在使用時才進行執行個體化。代碼如下:

餓漢式:

public class Singleton {

private static Singleton s = new Singleton();

private Singleton() {

}

public static Singleton getInstance() {

return s;

}

}

懶漢式:

public class Singleton {

private static Singleton s = null;

private Singleton() {

}

public static Singleton getInstance() {

if (s == null) {

s = new Singleton();

}

return s;

}

}

double check在餓漢式中是肯定不會存在的,是以我們讨論的就是懶漢式的情況。看一種情況:如果同時兩個線程同時通路getInstance()方法,線程A已經過了if判斷,正在初始化對象,但是可能由于構造時間比較長,還沒有構造完。這個時候s還沒有指向的對象,而同時線程B在進行判斷if的時候肯定也是null,自然也就可以通過,這時也可以進行new初始化,這樣的話就會生成兩個對象。因為是兩個對象,兩個線程中的資料肯定是不對的,是以會導緻一系列的資料不一緻的問題。這時我們可以考慮在方法上加synchronized關鍵字,或者在if外面加一層synchronized代碼塊,如下:

synchronized(Singleton.class) { // 注意這裡不能是this,因為是靜态對象,是以必須要用class

if (s == null) {

s = new Singleton();

}

}

這個時候我們發現并發問題是解決了,但是這時又會産生另外一個問題,如果s已經執行個體化了,就是說s != null了,而之後通路的對象依然要擷取鎖,這樣的話,在高并發的環境下,性能就會非常差,因為每個請求都要上鎖,必須挨個擷取鎖才行。此時我們可以在synchronized外面再加一層if(s == null)來判斷,就可以解決這個問題了,如果初始化完成之後,再進行判斷,則直接跳過,傳回s的對象,完整的代碼如下:

public static Singleton getInstance() {

if (s == null) {// 第一次判斷是否初始化

synchronized (Singleton.class) {// 加鎖是為了防止并發問題,隻允許一個線程進入,并進行初始化。

if (s == null) {// 如果此時仍不為null,就進行初始化,

s = new Singleton();

}

}

}

return s;

}

double check就是指做了兩次if判斷,第一次的主要是作用是為了解決性能問題,第二次才是為了解決并發的問題,防止生成兩個對象。