java設計模式-單例模式

原文連接:https://liushiming.cn/2020/03/01/java-design-pattern-singleton/html

概述

單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種模式涉及到一個單一的類,該類負責建立本身的對象,同時確保只有單個對象被建立。這個類提供了一種訪問其惟一的對象的方式,能夠直接訪問,不須要實例化該類的對象。java

特色:設計模式

  1. 單例類只能有一個實例。
  2. 單例類必須本身建立本身的惟一實例。
  3. 單例類必須給全部其餘對象提供這一實例。

線程不安全單例

餓漢模式

優勢:安全

  • 實現簡單
  • 線程安全

缺點:多線程

  • 可能形成資源浪費,即便不使用也會佔用內存,特別是實例比較大的時候

適用場景: 只有在初始化類的成本較低或程序老是須要類的實例時才使用併發

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方法,一個類能夠直接控制被反序列化的自身實例的類型和實例。

參考資料

高併發下線程安全的單例模式 jdk文檔-The readResolve Method 菜鳥教程-單例模式

相關文章
相關標籤/搜索