單例模式是 Java 中最簡單,也是最基礎,最經常使用的設計模式之一。在運行期間,保證某個類只建立一個實例,保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。下面就來說講Java中的N種實現單例模式的寫法。java
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } } 複製代碼
這是實現一個安全的單例模式的最簡單粗暴的寫法,這種實現方式咱們稱之爲餓漢式。之因此稱之爲餓漢式,是由於肚子很餓了,想立刻吃到東西,不想等待生產時間。這種寫法,在類被加載的時候就把Singleton實例給建立出來了。apache
餓漢式的缺點就是,可能在還不須要此實例的時候就已經把實例建立出來了,沒起到lazy loading的效果。優勢就是實現簡單,並且安全可靠。設計模式
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 複製代碼
相比餓漢式,懶漢式顯得沒那麼「餓」,在真正須要的時候再去建立實例。在getInstance方法中,先判斷實例是否爲空再決定是否去建立實例,看起來彷佛很完美,可是存在線程安全問題。在併發獲取實例的時候,可能會存在構建了多個實例的狀況。因此,須要對此代碼進行下改進。安全
public class SingletonSafe { private static volatile SingletonSafe singleton; private SingletonSafe() { } public static SingletonSafe getSingleton() { if (singleton == null) { synchronized (SingletonSafe.class) { if (singleton == null) { singleton = new SingletonSafe(); } } } return singleton; } } 複製代碼
這裏採用了雙重校驗的方式,對懶漢式單例模式作了線程安全處理。經過加鎖,能夠保證同時只有一個線程走到第二個判空代碼中去,這樣保證了只建立 一個實例。這裏還用到了volatile關鍵字來修飾singleton,其最關鍵的做用是防止指令重排。markdown
public class Singleton { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } } 複製代碼
經過靜態內部類的方式實現單例模式是線程安全的,同時靜態內部類不會在Singleton類加載時就加載,而是在調用getInstance()方法時才進行加載,達到了懶加載的效果。併發
彷佛靜態內部類看起來已是最完美的方法了,其實不是,可能還存在反射攻擊或者反序列化攻擊。且看以下代碼:工具
public static void main(String[] args) throws Exception { Singleton singleton = Singleton.getInstance(); Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton newSingleton = constructor.newInstance(); System.out.println(singleton == newSingleton); } 複製代碼
運行結果:spa
經過結果看,這兩個實例不是同一個,這就違背了單例模式的原則了。線程
除了反射攻擊以外,還可能存在反序列化攻擊的狀況。以下:設計
引入依賴:
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8.1</version> </dependency> 複製代碼
這個依賴提供了序列化和反序列化工具類。
Singleton類實現java.io.Serializable接口。
以下:
public class Singleton implements Serializable { private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; } public static void main(String[] args) { Singleton instance = Singleton.getInstance(); byte[] serialize = SerializationUtils.serialize(instance); Singleton newInstance = SerializationUtils.deserialize(serialize); System.out.println(instance == newInstance); } } 複製代碼
運行結果:
在effective java(這本書真的很棒)中說道,最佳的單例實現模式就是枚舉模式。利用枚舉的特性,讓JVM來幫咱們保證線程安全和單一實例的問題。除此以外,寫法還特別簡單。
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } } 複製代碼
調用方法:
public class Main { public static void main(String[] args) { Singleton.INSTANCE.doSomething(); } } 複製代碼
直接經過Singleton.INSTANCE.doSomething()的方式調用便可。方便、簡潔又安全。
以上列舉了多種單例模式的寫法,分析了其利弊之處。同時還介紹了目前最佳的單例寫法——枚舉模式,相信在將來,枚舉模式的單例寫法也會愈來愈流行。