面试: 单例模式

目录

一、饿汉单例(实现Serializable)

1、破坏单例的三种情况

(1)反射破坏单例

(2)反序列化破坏单例

(3)Unsafe破坏单例

2、饿汉单例(利用枚举实现)

二、懒汉单例(DCL实现)

1、多线程时会发生的错误

2、懒汉单例(内部类实现)

三、在哪些地方用到了单例模式


一、饿汉单例(实现Serializable)

在类执行初始化操作时,单例就会被创建,JVM会保证线程安全。

java 复制代码
public class Singleton implements Serializable{

	private Singleton() {
		System.out.println("111");
	}

	private static final Singleton INS = new Singleton();//先创建好对象
	
	public static Singleton getInstance() {//在需要使用时直接调用
		return INS;
	}
	
}

1、破坏单例的三种情况

(1)反射破坏单例
java 复制代码
reflection(Singleton.class);

反射后创建了两个对象,使得单例被破坏。要想预防,在单例接口中加上以下代码

java 复制代码
	private Singleton() {
		if(INS!=null) {
			throw new RuntimeException("单例不可被创建");
		}
		System.out.println("111");
	}

直接抛出异常。

(2)反序列化破坏单例
java 复制代码
serializable(Singleton.getInstance());
  • 将单例对象转换为字节流,又将字节流读取为对象,就实现了单例破坏;
  • 因为重新被读取的对象并不是原来的对象,而是新建的一个对象。
java 复制代码
	private static void serializable(Object instance) throws IOException,ClassNotFoundException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		Objectoutputstream oos = new Objectoutputstream(bos);
		oos.writeObject(instance);
		ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
		system.out.println("反序列化创建实例:" + ois.readObject());
	}

想要预防,在单例代码中实现这个方法

java 复制代码
public Object readResolve(){
    return INS;
}

这样就会利用这个方法的返回值作为结果返回,就不会用字节数组反序列化生成的对象了,这样就保证了对象的单例。

(3)Unsafe破坏单例
java 复制代码
unsafe(Singleton.class);

利用实例的类型来创建一个实例,不会走构造方法来创建。

java 复制代码
private static void unsafe(Class<?> clazz) throws InstantiationException {
    Object o = Unsafeutils.getUnsafe().allocateInstance(clazz);
    system.out.println( "Unsafe 创建实例:" + o);
}

2、饿汉单例(利用枚举实现)

java 复制代码
public enum Singleton{
    INS;
}
  • 这种方法实现可以避免被,反射和反序列化破坏单例;
  • 在反射时,java会做相应的检查,以阻止调用枚举类的构造。
  • 在反序列化时,java会直接返回枚举类实现的对象。
  • 挡不住unsafe。

二、懒汉单例(DCL实现)

java 复制代码
public class singleton implements serializable {
    private singleton() { system.out.println("private Singleton()");}
    private static volatile singleton INSTANCE = null;
    public static singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new singleton();
                }
            }
        }
    return INSTANCE;
}
  • 双端检索可以避免对象被重复创建;
  • 如果没有第二次检查null,就可能发生,一个等待锁的线程,进入创建对象的程序;
  • 添加volatile关键字可以避免指令重排序,因为在JVM中,构建对象的指令有一定的因果关系,如果不使用volatile方法的话,就会产生构建失败的情况。

1、多线程时会发生的错误

  • 在JVM执行INSTANCE=对象后,另一个线程直接返回了INSTANCE对象,但是这个对象的成员变量是没有被完全赋值的;
  • 在加上volatile后,指令后会加上一个内存屏障,这样就阻止了内存重新排序。

2、懒汉单例(内部类实现)

既能保证线程安全,又可以消灭懒汉在第一次使用时才创建对象的特性。

三、在哪些地方用到了单例模式

  • Runtime类
  • System类
  • Collection类
  • Comparator接口
  • Comparators类
相关推荐
long3162 分钟前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
Serene_Dream25 分钟前
JVM 并发 GC - 三色标记
jvm·面试
rannn_11129 分钟前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习
灵感菇_31 分钟前
Java HashMap全面解析
java·开发语言
qq_124987075333 分钟前
基于JavaWeb的大学生房屋租赁系统(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·计算机视觉·毕业设计·计算机毕业设计
短剑重铸之日39 分钟前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
若鱼19191 小时前
SpringBoot4.0新特性-Observability让生产环境更易于观测
java·spring
觉醒大王1 小时前
强女思维:着急,是贪欲外显的相。
java·论文阅读·笔记·深度学习·学习·自然语言处理·学习方法
努力学编程呀(๑•ี_เ•ี๑)1 小时前
【在 IntelliJ IDEA 中切换项目 JDK 版本】
java·开发语言·intellij-idea
码农小卡拉1 小时前
深入解析Spring Boot文件加载顺序与加载方式
java·数据库·spring boot