1.餓漢式
public final class SingletonObject1 {
private static final SingletonObject1 instance = new SingletonObject1();
private SingletonObject1() {
}
public static SingletonObject1 getInstance() {
return instance;
}
}
餓漢式的創建方法關鍵在於 instance作為類變量直接得到了初始化,這種方法的優點在於多線程環境下能夠百分百地保證同步,在多線程環境下不可能被實例化兩次,但是instance若是被加載后很長一段時間后才使用,就意味着instance實例開辟的堆內存會駐留更長的時間,所以更優的創建方式應是伴隨着懶加載的。
2.懶漢式
public final class SingletonObject2 {
private static SingletonObject2 instance == null;
private SingletonObject2() {
}
public static SingletonObject2 getInstance() {
if (null == instance)
instance = new SingletonObject2();
return instance;
}
}
所謂懶漢式就是在使用的時候才去創建,這樣就可以避免類在初始化時提前創建,但是這種方式有一個很大的缺點,在多線程的環境下,若一開始因為線程上下文切換的原因,兩個線程都通過了null==instance的if循環,這樣就是new出兩個實例,無法保證單例的唯一性,所以有下面第三種方法。
3.懶漢式+同步方法
public final class SingletonObject3 {
private static SingletonObject3 instance ;
private SingletonObject3() {
}
public synchronized static SingletonObject3 getInstance() {
if (null == instance)
instance = new SingletonObject3();
return instance;
}
}
這種方法通過添加同步控制既滿足了懶加載,又滿足了instance實例的唯一性,但是,添加了同步控制后getInstance方法的調用是串行化的,效率較低,因此引出第四種創建方式---Double Check方式
4.Double-Check
public final class SingletonObject4 {
Socket socket; //模仿一些資源的實例化
private static SingletonObject4 instance ;
private SingletonObject4() {
this.socket = new Socket();
}
public static SingletonObject4 getInstance() {
if (null == instance) {
synchronized (SingletonObject4.class) {
if (null == instance)
instance = new SingletonObject4();
}
}
return SingletonObject4.instance;
}
}
若有兩個線程通過了第一個Check循環,進入第二個Check循環是串行化的,只能有一個線程進入,這樣當這個線程創建完成后,另外的線程就無法通過第二個循環了,保證了實例的唯一性,隨后的線程也不會通過第一個Check循環,也就不會有同步控制的環節了。但是,這種方法也伴隨着一個缺點,它可能會引起空指針的異常。
假設這個單例創建有一些其他的資源,例如Socket、Connection,這些資源在構造函數中也會被實例化,那樣在創建單例的時候,就是要實例化自身還有Socket這些資源,那根據JVM的重排序和Happens-before原則,有可能會出現先實例化自身,再去實例化Socket這些資源,若在此時只實例化了自己的情況下,別的線程調用了這個單例中Socket這些資源的方法,而此時它們可能還沒有被實例化,這樣就會拋出空指針的異常,在此引出第五種創建方法----Volatile+Double-Check
5.Volatile+Double-Check
public final class SingletonObject5 {
private volatile static SingletonObject5 instance ;
private SingletonObject5() {
}
public static SingletonObject5 getInstance() {
if (null == instance) {
synchronized (SingletonObject5.class) {
if (null == instance)
instance = new SingletonObject5();
}
}
return SingletonObject5.instance;
}
}
volatile關鍵字可以防止重排序的發生,在此不對volatile作詳細介紹,通過volatile關鍵字,這種模式可以說是滿足懶加載、多線程下單例的唯一性、安全性的。
6.Holder方式
public final class SingletonObject6{
private SingletonObject6() {
}
private static class InstanceHolder {
private static SingletonObject6 instance = new SingletonObject6();
}
public static SingletonObject6 getInstance() {
return InstanceHolder.instance;
}
}
Holder這種方式是本人最喜歡的一種創建方式,它借助了類加載的特點,在SingletonObject6中並沒有instance的靜態成員,而是放置了靜態內部類InstanceHolder之中,因此SingletonObject6的初始化過程中並不會實例化instance,當Holder被主動引用的時候才會進行實例化,而在instance被實例化時是會在Java程序編譯器中收集至<cliinit>()方法中的,該方法是同步方法,且保證內存的可見性、原子性和順序性,可以說是餓漢方式的優化版,這種創建方式是最為廣泛的方式之一。
7.枚舉法
public enum EnumSingleton { Instance; public void method(){ } }
枚舉法是《Effective Java》中作者推薦的方式 ,這種方法極為簡單,因為枚舉類型本身是final的,不允許被繼承,且同樣是線程安全的,且只能被實例化一次和不用考慮序列化之類的問題,使用的時候可以直接EnumSingleton.Instance.method()就可以使用了,但是它不能實現懶加載,比如調用其中的靜態方法也是會實例化Instance的,讀者可以自行測試,但是也可以進行改造,讓枚舉充當Holder的角色增加懶加載的特性,代碼如下
public final class SingletonObject7 { private SingletonObject7() {} private enum Singleton { INSTANCE; private final SingletonObject7 instance; Singleton(){ instance = new SingletonObject7(); } public SingletonObject7 getInstance() { return instance; } } public static SingletonObject7 getInstance() { return Singleton.INSTANCE.getInstance(); } }
