前言
什么是单例模式?就是在一个应用程序中,一个类的实例有且仅有一个;这个类负责创建该类的实例;
一般来说单例是有状态的对象,比如全局设置、数据库dao实例、全局资源等,并且可以根据需求延迟加载或者即时加载;
即时加载单例模式
1、静态域单例(我不习惯别人说的饿汉、懒汉)
public classSingleton1 {private static Singleton1 instance = newSingleton1();//是有构造器, 防止被实例化
privateSingleton1() {
}public staticSingleton1 getInstance() {returninstance;
}public voiddoWhatever() {
}
}
使用方法:SingleTon1.getInstance()
特点:在类加载的时候就初始化好了,无线程安全问题;
即时加载,但是存在单例被破坏的风险,如使用反射、序列化
反射方式:
public static void main(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Singleton1.class;
Constructor constructor =clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton1 singleton1=constructor.newInstance();
System.out.println(singleton1==Singleton1.getInstance());
}
序列化方式(前提是单例类实现了Serializale):
public static voidmain(String[] args) {
Singleton1 singleton=Singleton1.getInstance();
File file= new File("srcmainesourcessingleton.txt");try (FileOutputStream fos = newFileOutputStream(file);
ObjectOutputStream oos= newObjectOutputStream(fos)) {
oos.writeObject(singleton);
}catch(IOException ex) {
ex.printStackTrace();
}try (FileInputStream fis = newFileInputStream(file);
ObjectInputStream ois= newObjectInputStream(fis)) {
Singleton1 singleton1=(Singleton1) ois.readObject();
System.out.println(singleton==singleton1);
}catch (IOException |ClassNotFoundException e) {
e.printStackTrace();
}
}
2、上述问题的解决方案
为了解决反射对单例造成的破坏,可以做如下修改:在私有构造方法中判断实例是否为null,否则抛运行时异常
//是有构造器, 防止被实例化
privateSingleton1() {if (instance != null) {throw newRuntimeException();
}
}
上述方法对序列化不起作用,这也从侧面验证了反序列化创建的对象不依赖类的构造器,而是由JVM创建的;
为了解决序列化对单例造成的破坏,可做如下修改:在序列化类中添加私有readResolve方法
privateObject readResolve() {returninstance;
}
虽然有以上方法可以解决上述问题,但是有更简便的单例模式 --- 枚举单例
3、枚举单例模式
枚举单例有如下几个有点:线程安全、能够防止反射和序列化带来的破坏、实现简单
public enumSingletonEnum {
INSTANCE;public voiddoWhatever() {
System.out.println("Single Enum.");
}
}
使用方法:SingletonEnum.INSTANCE.doWhatever()
验证下反射和序列化场景是否会破坏单例:
public static void main(String[] args) throwsNoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
SingletonEnum instance=SingletonEnum.INSTANCE;//验证两次获取的是否是同一个对象
System.out.println(instance ==SingletonEnum.INSTANCE);//序列化场景
File file = new File("srcmainesourcessingleton.txt");try (FileOutputStream fos = newFileOutputStream(file);
ObjectOutputStream oos= newObjectOutputStream(fos)) {
oos.writeObject(instance);
}catch(IOException ex) {
ex.printStackTrace();
}try (FileInputStream fis = newFileInputStream(file);
ObjectInputStream ois= newObjectInputStream(fis)) {
SingletonEnum instance2=(SingletonEnum) ois.readObject();
System.out.println(instance==instance2);
}catch (IOException |ClassNotFoundException e) {
e.printStackTrace();
}//反射场景
Class clazz = SingletonEnum.class;
Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
SingletonEnum instance1=constructor.newInstance();
System.out.println(instance==instance1);
}
运行结果:
为啥使用反射的时候会抛异常呢?这是因为反射的源码是这样写的:
那么又为啥枚举单例能够保证单例不被序列化破坏呢?
这是因为Java中规定,每个枚举变量在JVM中都是唯一的,并且Java还规定,枚举类在反序列化时使用枚举类的valueOf方法
枚举类反编译结果如下:
线程安全又是为什么?从反编译结果就能看出,INSTANCE是静态的,并且初始化时就创建了实例
未完待续。。。