JAVA常见的设计模式之单例模式
-
懒汉模式
懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。
标准的懒汉模式
class LazySingleton {// 私有成员属性private LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法public LazySingleton getLazySingleton() {// 如果成员属性为空,则创建实例if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}
单线程环境下,该单例模式只会有一个实例
public class TestDemo
{public static void main(String[] args) {LazySingleton lazySingleton = LazySingleton.getLazySingleton();LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();System.out.println(lazySingleton == lazySingleton2);}
}
运行结果:
多线程模式下,可能会产生多个实例
public class TestDemo
{public static void main(String[] args) {new Thread(() -> {LazySingleton lazySingleton = LazySingleton.getLazySingleton();System.out.println(lazySingleton);}).start();new Thread(() -> {LazySingleton lazySingleton = LazySingleton.getLazySingleton();System.out.println(lazySingleton);}).start();}
}
运行结果:
初步改进
class LazySingleton {// 私有成员属性private static LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法public synchronized static LazySingleton getLazySingleton() {// 如果成员属性为空,则创建实例if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}
}
缺点,每次调用方法都会加锁,效率低
再次改进
class LazySingleton {// 私有成员属性,使用volatile可以保证代码的有序性,防止指令重排private volatile static LazySingleton lazySingleton;// 私有构造方法private LazySingleton() {}// 公共的获取实例方法// 使用synchronized + 双重确认机制可以保证线程安全,但有可能存在指令重排,会导致创建多个实例public static LazySingleton getLazySingleton() {if (lazySingleton == null) {synchronized (LazySingleton.class) {if (lazySingleton == null) {lazySingleton = new LazySingleton();}}}return lazySingleton;}
}
静态类部类单例
/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}
静态类不类单例不会有线程安全问题,线程安全由类加载机制担保
恶汉模式
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间(搬运工)。
// 利用类加载机制保证线程安全
class HungrySingleton {private static HungrySingleton hungrySingleton = new HungrySingleton();private HungrySingleton() {}public static HungrySingleton getHungrySingleton() {return hungrySingleton;}
}
枚举单例模式
package com.hy.test.singletonDemo;public enum EnumSingletonDemo {INSTANCE;}class EnumTest {public static void main(String[] args) {EnumSingletonDemo instance = EnumSingletonDemo.INSTANCE;EnumSingletonDemo instance2 = EnumSingletonDemo.INSTANCE;System.out.println(instance == instance2);}
}
运行结果:
单例模式可能出现的问题(都会用静态类不类单例举例)
反射攻击
/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();Class clazz = InnerSingleton.class;Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();declaredConstructor.setAccessible(true);InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}
运行结果:
由此可见,反射生成了一个新的对象,不符合单例模式的定义
解决方法:在私有构造器中添加判断,如果已存在实例对象,抛出异常(也可进行其他操作,根据需求决定)
优化后的代码如下
/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();Class clazz = InnerSingleton.class;Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();declaredConstructor.setAccessible(true);InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类部类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例模式已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}
运行结果:
注意:只有恶汉模式与静态类部类能防止反射攻击
序列化相关问题
首先,我们对创建的实例进行序列化,代码如下:
/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();// 反序列化
/* ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);*/}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}
然后,我们进行反序列化,查看反序列化生成的实例跟单例的实例是否是同一个
/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试
/* ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();*/// 反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}}
运行结果:
由此可见,反序列化创建了一个新的实例
解决方法:Serializable的源码中给出了提示
/*** 测试demo** @auther Hy* @date 2020/8/25*/
public class TestDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();// 序列化测试
/* ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));oos.writeObject(innerSingleton);oos.close();*/// 反序列化ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();System.out.println(innerSingleton == innerSingleton1);}
}/*** 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性* 本质是一个懒汉模式,在类加载时才会初始化对象*/
class InnerSingleton implements Serializable {// 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,// 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败private static final long serialVersionUID = 7822769557659839582L;private static class InnerSingletonHolder {private static InnerSingleton innerSingleton = new InnerSingleton();}private InnerSingleton() {// 防止反射攻击,只有恶汉与静态类不类能防止反射攻击if (InnerSingletonHolder.innerSingleton != null) {throw new RuntimeException("单例已存在一个实例");}}public static InnerSingleton getInnerSingleton() {return InnerSingletonHolder.innerSingleton;}// 反序列化时,如果是单例模式,需要重写该方法,返回单例的实例,否则会获取到不同的对象Object readResolve() throws ObjectStreamException {return InnerSingletonHolder.innerSingleton;}
}
运行结果:
因此,在工作中推荐大家使用静态类部类单例模式,可以有效的防止反射攻击与序列化带来的相关问题