標籤:
單例的設計方式:
第一種:非消極式載入單例類
public class Singleton {
private Singleton() {}
private static final Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
第二種:同步消極式載入
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第三種:雙重檢測同步消極式載入
為處理原版非消極式載入方式瓶頸問題,我們需要對 instance 進行第二次檢查,目的是避開過多的同步(因為這裡的同步只需在第一次建立執行個體時才同步,一旦建立成功,以後擷取執行個體時就不需要同擷取鎖了),但在Java中行不通,因為同步塊外面的if (instance == null)可能看到已存在,但不完整的執行個體。JDK5.0以後版本若instance為volatile則可行:
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {// 1
if (instance == null) {// 2
instance = new Singleton();// 3
}
}
}
return instance;
}
}
雙重檢測鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平台記憶體模型。記憶體模型允許所謂的“無序寫入”,這也是失敗的一個主要原因。
為展示此事件的發生情況,假設程式碼 instance =new Singleton(); 執行了下列虛擬碼:
mem = allocate(); //為單例對象分配記憶體空間.
instance = mem; //注意,instance 引用現在是非空,但還未初始化
ctorSingleton(instance); //為單例對象通過instance調用建構函式
這段虛擬碼不僅是可能的,而且是一些 JIT 編譯器上真實發生的。執行的順序是顛倒的,但鑒於當前的記憶體模型,這也是允許發生的。JIT 編譯器的這一行為使雙重檢查鎖定的問題只不過是一次學術實踐而已。
但是從JAVA2以後,JMM發生了根本的改變,分配空間,初始化,調用構造方法只會線上程的工作儲存區完成,在沒有
向主儲存區複製賦值時,其它線程絕對不可能見到這個過程.而這個欄位複製到主存區的過程,更不會有分配空間後
沒有初始化或沒有調用構造方法的可能.在JAVA中,一切都是按引用的值複製的.向主儲存區同步其實就是把線程工作
儲存區的這個已經構造好的對象有壓縮堆地址值COPY給主儲存區的那個變數.這個過程對於其它線程,要麼是resource
為null,要麼是完整的對象.絕對不會把一個已經分配空間卻沒有構造好的對象讓其它線程可見.
第四種:使用ThreadLocal修複雙重檢測
藉助於ThreadLocal,將臨界資源(需要同步的資源)線程局部化,具體到本例就是將雙重檢測的第一層檢測條件 if (instance == null) 轉換為了線程局部範圍內來作。這裡的ThreadLocal也只是用作標示而已,用來標示每個線程是否已訪問過,如果訪問過,則不再需要走同步塊,這樣就提高了一定的效率。但是ThreadLocal在1.4以前的版本都較慢,但這與volatile相比卻是安全的。
public class Singleton {
private static final ThreadLocal perThreadInstance = new ThreadLocal();
private static Singleton singleton ;
private Singleton() {}
public static Singleton getInstance() {
if (perThreadInstance.get() == null){
// 每個線程第一次都會調用
createInstance();
}
return singleton;
}
private static final void createInstance() {
synchronized (Singleton.class) {
if (singleton == null){
singleton = new Singleton();
}
}
perThreadInstance.set(perThreadInstance);
}
}
第五種:使用內部類實現消極式載入
為了做到真真的消極式載入,雙重檢測在Java中是行不通的,所以只能藉助於另一類的類載入加消極式載入:
public class Singleton {
private Singleton() {}
public static class Holder {
// 這裡的私人沒有什麼意義
/* private */static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
// 外圍類能直接存取內部類(不管是否是靜態)的私人變數
return Holder.instance;
}
}
來源: <單例模式與雙重檢測 - 設計模式 - Java - ITeye論壇>
來自為知筆記(Wiz)
java並發4-單例設計方法