實做 Singleton 的方法有很多種,Java 1.5 以上可以使用
Double-checked locking,但使用鎖會影響程式效率,如何不用鎖又能達到 Singleton 呢? Initialization-on-demand holder 就是一種常見的程式技巧,以下列出 Eager initialization, Static block initialization 與 Initialization-on-demand holder 做比較
public class EagerInitialization {
public static final EagerInitialization INSTANCE = new EagerInitialization();
public static final String TEST = "test";
private EagerInitialization() {
System.out.println("constructor");
}
public static EagerInitialization getInstance() {
System.out.println("getInstance");
return INSTANCE;
}
}
public class StaticBlockInitialization {
public static StaticBlockInitialization instance;
public static final String TEST = "test";
private StaticBlockInitialization() {
System.out.println("constructor");
}
static {
try {
instance = new StaticBlockInitialization();
}
catch (Exception e) {
e.printStackTrace();
}
}
public static StaticBlockInitialization getInstance() {
System.out.println("getInstance");
return instance;
}
}
public class InitializationOnDemandHolder {
public static final String TEST = "test";
private InitializationOnDemandHolder() {
System.out.println("constructor");
}
private static class SingletonHolder {
public static final InitializationOnDemandHolder INSTANCE = new InitializationOnDemandHolder();
}
public static InitializationOnDemandHolder getInstance() {
System.out.println("getInstance");
return SingletonHolder.INSTANCE;
}
}
import org.junit.Test;
public class TestRun {
@Test
public void testEagerInitialization() {
System.out.println("testEagerInitialization start");
System.out.println(EagerInitialization.TEST);
EagerInitialization.getInstance();
System.out.println("testEagerInitialization end");
System.out.println("------------------------------------");
}
@Test
public void testStaticBlockInitialization() {
System.out.println("testStaticBlockInitialization start");
System.out.println(StaticBlockInitialization.TEST);
StaticBlockInitialization.getInstance();
System.out.println("testStaticBlockInitialization end");
System.out.println("------------------------------------");
}
@Test
public void testInitializationOnDemandHolder() {
System.out.println("testInitializationOnDemandHolder start");
System.out.println(InitializationOnDemandHolder.TEST);
InitializationOnDemandHolder.getInstance();
System.out.println("testInitializationOnDemandHolder end");
System.out.println("------------------------------------");
}
}
output
testEagerInitialization start
test
constructor
getInstance
testEagerInitialization end
------------------------------------
testStaticBlockInitialization start
test
constructor
getInstance
testStaticBlockInitialization end
------------------------------------
testInitializationOnDemandHolder start
test
getInstance
constructor
testInitializationOnDemandHolder end
------------------------------------
由此可看出 Initialization-on-demand holder 的 lazy initialization 效果是三種方法中最好的
如果我們把上面程式裡的 TEST 變數 關鍵字 final 拿掉
public static final String TEST = "test";
修改成
public static String TEST = "test";
除了 testInitializationOnDemandHolder 輸出結果一樣之外, 其他兩個的輸出次序會改變, 進而影響到 lazy initialization 的結果,以下是拿掉 final 關鍵字的輸出結果
testEagerInitialization start
constructor
test
getInstance
testEagerInitialization end
------------------------------------
testStaticBlockInitialization start
constructor
test
getInstance
testStaticBlockInitialization end
------------------------------------
testInitializationOnDemandHolder start
test
getInstance
constructor
testInitializationOnDemandHolder end
------------------------------------
當存取到非 final 的 static 變數時, JVM 會立刻初始化其他 static 變數,因此使用 Eager initialization, Static block initialization 必須要注意到這個問題,相形之下 Initialization-on-demand holder 的好處就顯現出來了,就算其他程式設計師在程式中添加其他變數也不會影響到程式輸出結果
參考
http://en.wikipedia.org/wiki/Singleton_pattern