單例模式(下)---聊一聊單例模式的幾種寫法
在上一篇文章ofollow,noindex" target="_blank">單例模式(上)—-如何優雅地保證執行緒安全問題 中,我們採取了懶漢式 寫法來寫我們的單例模式,並且重點講解了懶漢式中執行緒安全的問題。這篇我們來講講單例模式中的其他幾種寫法。
上篇文章中,方法和變數的宣告都忘了加上“static”的宣告,這裡提醒一下。
懶漢式
懶漢式在上節我們已經講過了,直接給出程式碼:
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; } }
懶漢式這種方式需要我們來自己加鎖,保證執行緒安全的問題。
不過就算我們保證了執行緒安全,這種寫法還是無法保證存在唯一一個物件例項。因為別人還是可以通過反射 的方式來建立一個新的物件。我寫個示例:
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。
所以懶漢式這種方式還是存在一些缺點的。
餓漢式
所謂餓漢式,就是一開始把物件例項創建出來,而不是等getInstance這個方法被呼叫才來建立物件。程式碼如下:
public class Singleton2 { private static Singleton2 instance = new Singleton2(); //私有構造器 private Singleton2(){}; public static Singleton2 getInstance() { return instance; } }
餓漢式與懶漢式相比,我們不用管執行緒安全的問題,程式碼看起來也比較簡潔。
但是,由於物件一開始就被創建出來了,假如我們從頭到尾都不呼叫getInstance()這個方法,那麼這個物件就白建立了。
當然,和懶漢式一樣,餓漢式也存在反射 問題。
總結一下餓漢式的一些問題:
1、有可能出現物件白白浪費的情況。
2、和懶漢式一樣,無法組織反射問題。
採用靜態內部類的寫法
直接上程式碼
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)
所以,這種列舉的方式可以說的用的最多的一種方式了,唯一的缺點就是物件一開始就被建立,可能出現白白浪費沒有用到物件的情況。
不過,總體上,還是推薦採用列舉的方式來寫。
完
獲取更多原創 文章,可以關注下我的公眾號:苦逼的碼農 ,我會不定期分享一些資源和軟體等。後臺回覆禮包 送你一份時下熱門的資源大禮包。同時也感謝把文章介紹給更多需要的人。