歡迎關注下文:單例模式不是一件小事,快回來看看。
單例模式是一種創建型模式,某個類采用單例模式,則在這個類被創建后,只可能產生一個實例供外部訪問,並且提供一個全局的訪問點。
主要思想如下:
- 將構造方法私有化( 聲明為 private ),這樣外界不能隨意 new 出新的實例對象;
- 聲明一個私有的靜態的實例對象,供外界使用;
- 提供一個公開的方法,讓外界獲得該類的實例對象。
具體實現代碼如下:
代碼①
public class Singleton { /** * 構造方法私有化 */ private Singleton() { } /** * 定義一個私有的靜態的實例 */ private static Singleton sSingleton = new Singleton(); /** * 提供靜態的方法給外界訪問 * * @return */ public static Singleton getInstance() { return sSingleton; } }
上面代碼①便是傳說中的餓漢式單例模式。餓漢式有一個缺點是在類一加載的時候,就實例化,提前占用了系統資源。為此,我們可以稍微優化一下:
代碼②
public class Singleton { private Singleton() { }
private static Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { sSingleton = new Singleton(); } return sSingleton; } }
這樣便成了“懶人”的使用方式,在需要使用該類的時候,再實例化,解決了上面所說的缺點,稱為“懶漢式”。不過,這樣存在線程安全問題,可能會造成重復創建對象,與單例模式的思想相悖。所以我們需要改進該方法:
代碼③
public class Singleton { private Singleton() { } private static Singleton sSingleton; public synchronized static Singleton getInstance() { if (sSingleton == null) { sSingleton = new Singleton(); } return sSingleton; } }
OK,給方法加上同步,就可以避免並發環境下的問題了。這就是傳說中的懶漢式單例模式。同步該方法后,可以避免多個線程重復創建對象,因為每次只能有一個線程去訪問該方法,其他線程必須等待,假設現在實例對象已經初始化了,不需要再創建了,上面的方式就顯得效率低了。所以,該方法可以繼續做如下優化:
代碼④
public class Singleton { private Singleton() { } /** * volatile is since JDK5 */ private static volatile Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { synchronized (Singleton.class) { // 未初始化,則初始instance變量 if (sSingleton == null) { sSingleton = new Singleton(); } } } return sSingleton; } }
上面的代碼,不再同步方法了,采用雙重判斷,同步代碼塊,這樣大大地提高了懶漢式單例模式的效率,特別要注意的是:聲明對象的時候,使用了一個關鍵字:volatile,該關鍵字是從JDK1.5之后新加入的,不加該關鍵字,編譯器可能會失去大量優化的機會或者可能會在編譯時出現一些不可預知的錯誤。
此外,還有一種靜態內部類實現單例模式的方法,如下:
代碼⑤
public class Singleton { private Singleton () { } private static class InnerClassSingleton {
private final static Singleton sSingleton = new Singleton(); } public static Singleton getInstance() { return InnerClassSingleton.sSingleton; } }
該方法簡單明了,不需要同步,代碼也不是很復雜。上面代碼中靜態內部類 InnerClassSingleton 在 Singleton 類加載的時候並不會加載,下面修改代碼④,進行驗證:
public class Singleton { public Singleton () { } private static class InnerClassSingleton { static{ System.out.println("-----InnerClassSingleton 已經加載了...."); } private final static Singleton sSingleton = new Singleton(); } public static Singleton getInstance() { System.out.println(InnerClassSingleton.sSingleton.hashCode()); return InnerClassSingleton.sSingleton; } }
注意特意將 Singleton 的構造方法公開化了,再編寫一個測試類:
public class Test { public static void main(String[] args) { new Singleton(); System.out.println(Singleton.class); } }
運行代碼,打印結果只有一行:
class com.examle.joy.Singleton
這說明靜態內部類並沒有加載,再次修改測試類:
public class Test { public static void main(String[] args) { System.out.println(Singleton.getInstance()); System.out.println(Singleton.getInstance()); new Thread(new Runnable() { @Override public void run() { System.out.println(Singleton.getInstance()); } }).start(); } }
這次的運行結果如下:
-----InnerClassSingleton 已經加載了...
com.examle.joy.Singleton@203b4f0e
com.examle.joy.Singleton@203b4f0e
com.examle.joy.Singleton@203b4f0e
上面的打印結果是符合我們預期的。
最后總結一下:代碼⑤是單例模式最佳的實現方法,在實際開發中,餓漢式代碼簡潔,容易理解,用的比較多,如果不涉及並發操作的話,也可以使用懶漢式代碼②,設計到多線程並發問題,用懶漢式的話,需要使用代碼④,代碼⑤顯得很優雅,是個人比較推薦的一種方式。最后值得一提的是,所有的單例模式,都只能用在非反射場景中,因為利用反射,成員變量或者方法即便聲明為 private,也可以被外部訪問。