面试: 单例模式

目录

一、饿汉单例(实现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类
相关推荐
Seven971 分钟前
NIO的零拷贝如何实现高效数据传输?
java
哈里谢顿11 小时前
0305乒乓xx agent运维开发岗面试记录
面试
哈里谢顿11 小时前
0309面试二总结
面试
哈里谢顿11 小时前
0309面试一记录
面试
哈里谢顿11 小时前
0310面试二记录
面试
哈里谢顿11 小时前
0310面试记录一
面试
boooooooom14 小时前
讲清 Proxy + effect + track/trigger 流程
javascript·vue.js·面试
架构师沉默14 小时前
别又牛逼了!AI 写 Java 代码真的行吗?
java·后端·架构
豆苗学前端15 小时前
彻底讲透浏览器缓存机制,吊打面试官
前端·javascript·面试
zone773915 小时前
006:RAG 入门-面试官问你,RAG 为什么要切块?
后端·算法·面试