原文連接:https://liushiming.cn/2020/03/01/java-design-pattern-singleton/html
概述
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。java
特色:設計模式
- 單例類只能有一個實例。
- 單例類必須本身建立本身的惟一實例。
- 單例類必須給全部其餘對象提供這一實例。
線程不安全單例
餓漢模式
優勢:安全
- 實現簡單
- 線程安全
缺點:多線程
- 可能形成資源浪費,即便不使用也會佔用內存,特別是實例比較大的時候
適用場景: 只有在初始化類的成本較低或程序老是須要類的實例時才使用併發
public final class EagerSingleton { private static EagerSingleton singObj = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getSingleInstance() { return singObj; } }
懶漢模式
優勢:ide
- 對象僅在須要時被建立,無內存和cpu的浪費
缺點:高併發
- 非線程安全,多個線程同時走到
if (null == singObj )
就會建立多個實例
適用場景: 非多線程環境測試
public final class LazySingleton { private static LazySingleton singObj = null; private LazySingleton() { } public static LazySingleton getSingleInstance() { if (null == singObj ) { singObj = new LazySingleton(); } return singObj; } }
線程安全單例
線程安全單例測試方法,新建多個線程同時實例化單例類,看hashCode是否一致:ui
class MyThread extends Thread { @Override public void run() { System.out.println(Singleton.getInstance().hashCode()); } public static void main(String[] args) { MyThread[] mts = new MyThread[10]; for (int i = 0; i < mts.length; i++) { mts[i] = new MyThread(); } for (int j = 0; j < mts.length; j++) { mts[j].start(); } } }
同步方法
優勢:
- 對象僅在須要時被建立,無內存和cpu的浪費
- 線程安全,由於給方法加了
Synchronized
同步鎖
缺點:
- 方法上加鎖大幅限制了多線程的效率
適用場景: 不建議使用
public final class ThreadSafeSingleton { private static ThreadSafeSingleton singObj = null; private ThreadSafeSingleton() { } public static synchronized ThreadSafeSingleton getInstance() { if (null == singObj) { singObj = new ThreadSafeSingleton(); } return singObj; } }
同步代碼塊、雙檢查
優勢:
- 對象僅在須要時被建立,無內存和cpu的浪費
- 線程安全。由於給須要加鎖的代碼塊加了
Synchronized
同步鎖,且代碼塊中有雙檢查
缺點:
- 代碼略繁瑣
適用場景: 基本上都適用,是一種較優的單例模式實現
class DoubleCheckedSingleton { private static DoubleCheckedSingleton singObj = null; private DoubleCheckedSingleton() { } public static DoubleCheckedSingleton getInstance() { if (null == singObj) { synchronized (DoubleCheckedSingleton.class) { if (null == singObj) { singObj = new DoubleCheckedSingleton(); } } } return singObj; } }
內部類
優勢:
- 對象僅在須要時被建立,無內存和cpu的浪費
- 線程安全
- 代碼簡潔
缺點:
- 靜態內部類雖然保證了單例在多線程併發下的線程安全性,可是在遇到序列化對象時,默認的方式運行獲得的結果是多例的。
適用場景: 不涉及序列化與反序列化的場景
public class Singleton { private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
靜態代碼塊
優勢:
- 靜態代碼塊在使用類的時候被執行一次 ,且僅執行一次,實現了延遲實例化
- 線程安全
缺點:
- 靜態內部類雖然保證了單例在多線程併發下的線程安全性,可是在遇到序列化對象時,默認的方式運行獲得的結果是多例的。
適用場景: 推薦
public class MySingleton{ private static MySingleton instance = null; private MySingleton(){} static{ instance = new MySingleton(); } public static MySingleton getInstance() { return instance; } }
序列化與反序列化單例問題
在不涉及序列化與反序列化的場景中,以上線程安全的單例類實現都沒有問題,可是在反序列化時,默認獲得的結果是多例的
單例實現:
public class Singleton { private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
測試代碼:
public class SingletonTest { public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); File file = new File("MySingleton.txt"); try { FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton); fos.close(); oos.close(); System.out.println(singleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); Singleton rSingleton = (Singleton) ois.readObject(); fis.close(); ois.close(); System.out.println(rSingleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
結果:
1023892928 193064360
從結果中咱們發現,序列號對象的hashCode和反序列化後獲得的對象的hashCode值不同,說明反序列化後返回的對象是從新實例化的,單例被破壞了。那怎麼來解決這一問題呢?
解決辦法就是在反序列化的過程當中使用readResolve()方法,單例實現的代碼以下:
class Singleton implements Serializable { private static final long serialVersionUID = 1L; private static class SingletonHolder { public final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } protected Object readResolve() throws ObjectStreamException { System.out.println("調用了readResolve方法!"); return getInstance(); } }
結果:
1023892928 調用了readResolve方法! 1023892928
對於Serializable和Externalizable類,readResolve方法容許類在將從流中讀取的對象返回給調用者以前替換/解析它。經過實現readResolve方法,一個類能夠直接控制被反序列化的自身實例的類型和實例。