大家好,又见面了,我是你们的朋友全栈君。
JAVA常见的设计模式之单例模式
- 懒汉模式
懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。
标准的懒汉模式
代码语言:javascript复制class LazySingleton {
// 私有成员属性
private LazySingleton lazySingleton;
// 私有构造方法
private LazySingleton() {
}
// 公共的获取实例方法
public LazySingleton getLazySingleton() {
// 如果成员属性为空,则创建实例
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
单线程环境下,该单例模式只会有一个实例
代码语言:javascript复制public class TestDemo
{
public static void main(String[] args) {
LazySingleton lazySingleton = LazySingleton.getLazySingleton();
LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();
System.out.println(lazySingleton == lazySingleton2);
}
}
运行结果:
多线程模式下,可能会产生多个实例
代码语言:javascript复制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();
}
}
运行结果:
初步改进
代码语言:javascript复制class LazySingleton {
// 私有成员属性
private static LazySingleton lazySingleton;
// 私有构造方法
private LazySingleton() {
}
// 公共的获取实例方法
public synchronized static LazySingleton getLazySingleton() {
// 如果成员属性为空,则创建实例
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
缺点,每次调用方法都会加锁,效率低
再次改进
代码语言:javascript复制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;
}
}
静态类部类单例
代码语言:javascript复制/**
* 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
* 本质是一个懒汉模式,在类加载时才会初始化对象
*/
class InnerSingleton implements Serializable {
private static class InnerSingletonHolder {
private static InnerSingleton innerSingleton = new InnerSingleton();
}
private InnerSingleton() {
}
public static InnerSingleton getInnerSingleton() {
return InnerSingletonHolder.innerSingleton;
}
}
静态类不类单例不会有线程安全问题,线程安全由类加载机制担保
恶汉模式
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间(搬运工)。
代码语言:javascript复制// 利用类加载机制保证线程安全
class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getHungrySingleton() {
return hungrySingleton;
}
}
枚举单例模式
代码语言:javascript复制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);
}
}
运行结果:
单例模式可能出现的问题(都会用静态类不类单例举例)
反射攻击
代码语言:javascript复制/**
* 测试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;
}
}
运行结果:
由此可见,反射生成了一个新的对象,不符合单例模式的定义
解决方法:在私有构造器中添加判断,如果已存在实例对象,抛出异常(也可进行其他操作,根据需求决定)
优化后的代码如下
代码语言:javascript复制/**
* 测试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;
}
}
运行结果:
注意:只有恶汉模式与静态类部类能防止反射攻击
序列化相关问题
首先,我们对创建的实例进行序列化,代码如下:
代码语言:javascript复制/**
* 测试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;
}
}
然后,我们进行反序列化,查看反序列化生成的实例跟单例的实例是否是同一个
代码语言:javascript复制/**
* 测试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的源码中给出了提示
代码语言:javascript复制/**
* 测试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;
}
}
运行结果:
因此,在工作中推荐大家使用静态类部类单例模式,可以有效的防止反射攻击与序列化带来的相关问题
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/169696.html原文链接:https://javaforall.cn