面试: 单例模式

目录

一、饿汉单例(实现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类
相关推荐
沙子迷了蜗牛眼22 分钟前
当展示列表使用 URL.createObjectURL 的创建临时图片、视频无法加载问题
java·前端·javascript·vue.js
ganshenml25 分钟前
【Android】 开发四角版本全解析:AS、AGP、Gradle 与 JDK 的配套关系
android·java·开发语言
我命由我1234525 分钟前
Kotlin 运算符 - == 运算符与 === 运算符
android·java·开发语言·java-ee·kotlin·android studio·android-studio
小途软件31 分钟前
ssm327校园二手交易平台的设计与实现+vue
java·人工智能·pytorch·python·深度学习·语言模型
alonewolf_9935 分钟前
Java类加载机制深度解析:从双亲委派到热加载实战
java·开发语言
追梦者12337 分钟前
springboot整合minio
java·spring boot·后端
云游40 分钟前
Jaspersoft Studio community edition 7.0.3的应用
java·报表
帅气的你1 小时前
Spring Boot 集成 AOP 实现日志记录与接口权限校验
java·spring boot
zhglhy1 小时前
Spring Data Slice使用指南
java·spring
win x1 小时前
Redis 主从复制
java·数据库·redis