在上一篇文章 單例模式(上)---如何優雅地保證線程安全問題中,咱們採起了懶漢式寫法來寫咱們的單例模式,而且重點講解了懶漢式中線程安全的問題。這篇咱們來說講單例模式中的其餘幾種寫法。java
上篇文章中,方法和變量的聲明都忘了加上「static」的聲明,這裏提醒一下。安全
懶漢式在上節咱們已經講過了,直接給出代碼:bash
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){};
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
複製代碼
懶漢式這種方式須要咱們來本身加鎖,保證線程安全的問題。ui
不過就算咱們保證了線程安全,這種寫法仍是沒法保證存在惟一一個對象實例。由於別人仍是能夠經過反射的方式來建立一個新的對象。我寫個示例:spa
public class Singleton {
public static void main(String[] args) throws Exception{
//得到構造器
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
//把構造器設置爲可訪問
c.setAccessible(true);
//建立兩個實例對象
Singleton s1 = c.newInstance();
Singleton s2 = c.newInstance();
//比較下兩個實例是否相等
System.out.println(s1 == s2);
}
}
複製代碼
打印結果:false。線程
因此懶漢式這種方式仍是存在一些缺點的。code
所謂餓漢式,就是一開始把對象實例建立出來,而不是等getInstance這個方法被調用纔來建立對象。代碼以下:對象
public class Singleton2 {
private static Singleton2 instance = new Singleton2();
//私有構造器
private Singleton2(){};
public static Singleton2 getInstance() {
return instance;
}
}
複製代碼
餓漢式與懶漢式相比,咱們不用管線程安全的問題,代碼看起來也比較簡潔。資源
可是,因爲對象一開始就被建立出來了,假如咱們從頭至尾都不調用getInstance()這個方法,那麼這個對象就白建立了。get
固然,和懶漢式同樣,餓漢式也存在反射問題。
總結一下餓漢式的一些問題:
一、有可能出現對象白白浪費的狀況。
二、和懶漢式同樣,沒法組織反射問題。
直接上代碼
public class Singleton3 {
//靜態內部類
private static class LazyHolder{
private static Singleton3 instance = new Singleton3();
}
//私有構造器
private Singleton3(){};
public static Singleton3 getInstance() {
return LazyHolder.instance;
}
}
複製代碼
因爲外部類沒法訪問靜態內部類,所以只有當外部類調用Singleton.getInstance()方法的時候,才能獲得instance實例。
而且,instance實例對象初始化的時機並非在Singleton被加載的時候,而是當getInstance()方法被調用的時候,靜態內部類纔會被加載,這時instance對象纔會被初始化。而且也是線程安全的。
因此,與餓漢式相比,經過靜態內部類的方式,能夠保證instance實例對象不會被白白浪費。
可是,它仍然存在反射問題。
直接上代碼:
public enum Singleton4 {
//通常用大寫的了,不過爲了和前面的統一
//我就用小寫的了
instance;
}
複製代碼
枚舉的方式簡單吧?一行代碼就搞定了,不過和餓漢式同樣,因爲一開始instance實例就被建立了,因此有可能出現白白浪費的狀況。
可是,經過枚舉的方式,不只代碼簡單,線程安全,並且JVM還能阻止反射獲取枚舉類的私有構造器。
下面作個實驗
public enum Singleton4 {
//通常用大寫的了,不過爲了和前面的統一
//我就用小寫的了
instance;
public static void main(String[] args) throws Exception{
//得到構造器
Constructor<Singleton4> c = Singleton4.class.getDeclaredConstructor();
//把構造器設置爲可訪問
c.setAccessible(true);
//建立兩個實例對象
Singleton4 s1 = c.newInstance();
Singleton4 s2 = c.newInstance();
//比較下兩個實例是否相等
System.out.println(s1 == s2);
}
}
複製代碼
結果出現了異常:
Exception in thread "main" java.lang.NoSuchMethodException: singleton.Singleton4.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at singleton.Singleton4.main(Singleton4.java:12)
複製代碼
因此,這種枚舉的方式能夠說的用的最多的一種方式了,惟一的缺點就是對象一開始就被建立,可能出現白白浪費沒有用到對象的狀況。
不過,整體上,仍是推薦採用枚舉的方式來寫。
完
獲取更多原創文章,能夠關注下個人公衆號:苦逼的碼農,我會不按期分享一些資源和軟件等。同時也感謝把文章介紹給更多須要的人。