单例模式实现方式:懒汉式、饿汉式、双重检查、枚举、静态内部类;
懒汉式:
/*** 懒汉式单例模式* @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);
运行:
这也是为什么枚举是实现单例模式最好的方式原因之一。