面试: 单例模式

目录

一、饿汉单例(实现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类
相关推荐
JH30734 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
Coder_Boy_5 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
invicinble6 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
发现一只大呆瓜6 小时前
前端性能优化:图片懒加载的三种手写方案
前端·javascript·面试
较真的菜鸟6 小时前
使用ASM和agent监控属性变化
java
黎雁·泠崖6 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言
qq_12498707537 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_8 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.8 小时前
Day06——权限认证-项目集成
java
瑶山8 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard