單例模式實現方式:懶漢式、餓漢式、雙重檢查、枚舉、靜態內部類;
懶漢式:
/*** 懶漢式單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public class LazySingleton implements Serializable {private static LazySingleton lazySingleton=null;private LazySingleton(){}//特點:第一次調用才初始化,避免內存浪費。public synchronized static LazySingleton getInstance(){if(lazySingleton==null){lazySingleton=new LazySingleton();}return lazySingleton;}
}
餓漢式:
/**餓漢式單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public class HungrySingleton {static final HungrySingleton hungrySingleton=new HungrySingleton();private HungrySingleton(){}//特點:類加載時就初始化,線程安全public static HungrySingleton getInstance(){return hungrySingleton;}
}
雙重檢查:
和懶漢式的區別就是,鎖的范圍減小了,防止多線程場景下創建多個實例,所以又加了一層判斷。
/**雙重檢查單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public class DoubleCheckSingletion {private static DoubleCheckSingletion doubleCheckSingletion=null;private DoubleCheckSingletion(){}//特點:安全且在多線程情況下能保持高性能public static DoubleCheckSingletion getInstance(){if(doubleCheckSingletion==null){synchronized (DoubleCheckSingletion.class){if(doubleCheckSingletion==null){doubleCheckSingletion=new DoubleCheckSingletion();}}}return doubleCheckSingletion;}
}
枚舉:
/**枚舉形單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public enum EnumSingleton {INSTANCE;//特點:自動支持序列化機制,絕對防止多次實例化public static EnumSingleton getInstance(){return INSTANCE;}
}
靜態內部類:
/**靜態內部類單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public class StaticInnerClassSingleton {private static class InnerClass{private static StaticInnerClassSingleton staticInnerClassSingleton=new StaticInnerClassSingleton();}private StaticInnerClassSingleton(){}public static StaticInnerClassSingleton getInstance(){return InnerClass.staticInnerClassSingleton;}
}
序列化或反射破壞單列模式:
通過序列化反序列化或反射可以破壞除枚舉以外的其它實現方式:
LazySingleton instance = LazySingleton.getInstance();//序列化破壞單例模式ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singletion"));oos.writeObject(instance);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singletion"));LazySingleton instance2 = (LazySingleton) ois.readObject();System.out.println(instance);System.out.println(instance2);System.out.println(instance2==instance);
運行:
解決反序列化破壞單列模式
解決方法只需要在單例類里加上一個readResolve()方法即可,原因就是在反序列化的過程中,會檢測readResolve()方法是否存在,如果存在的話就會反射調用readResolve()這個方法。
private Object readResolve(){return lazySingleton;}
使用枚舉單例模式天然就可以防止序列化破壞單例模式:
EnumSingleton instance = EnumSingleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singletion"));oos.writeObject(instance);ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singletion"));EnumSingleton instance2 = (EnumSingleton) ois.readObject();System.out.println(instance);System.out.println(instance2);System.out.println(instance==instance2);
運行:
為什么?
-
枚舉類的序列化保證單例: Java枚舉類型在序列化和反序列化時會被特殊處理。在序列化的過程中,只是將枚舉對象的名字(即枚舉常量的名字)寫入到序列化文件中;在反序列化的過程中,通過名字來獲取枚舉對象。這保證了在反序列化過程中無論如何都只會得到枚舉中定義的枚舉常量,而不會重新創建新的對象。因此,枚舉類型的單例模式在反序列化過程中也能保持單例的狀態,不會被破壞。
-
禁止反射創建多個實例: 枚舉類型的單例模式天然地禁止了通過反射機制來創建多個實例。枚舉類型的構造器是私有的,并且編譯器會確保枚舉常量只能被實例化一次。如果嘗試使用反射來調用枚舉類型的私有構造器來創建新的實例,會拋出
IllegalArgumentException
異常,從而保證了單例的唯一性。
?
反射破壞單例模式:
/**反射破壞單例模式* @author: 小手WA涼* @create: 2024-07-06*/
public class BrokenSingletonTest2 {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {LazySingleton instance = LazySingleton.getInstance();Class<LazySingleton> lazySingletonClass = LazySingleton.class;Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();constructor.setAccessible(true);LazySingleton refInstance = constructor.newInstance();System.out.println(instance);System.out.println(refInstance);System.out.println(instance==refInstance);}
}
?運行:
枚舉天然也可以防止反射破壞單例模式:
EnumSingleton instance = EnumSingleton.getInstance();Class<EnumSingleton> singletonClass = EnumSingleton.class;Constructor<EnumSingleton> constructor = singletonClass.getDeclaredConstructor(String.class,int.class);constructor.setAccessible(true);EnumSingleton refInstance = constructor.newInstance();System.out.println(instance);System.out.println(refInstance);System.out.println(instance==refInstance);
運行:
這也是為什么枚舉是實現單例模式最好的方式原因之一。