欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

面试装X之--单例模式深度探索(玩转面试官)

程序员文章站 2024-03-24 09:46:10
...

之前详细总写过(单例模式)的己种写法,今天对这几种方法进行更深层次的总结

单例模式(Singleton Pattern)

是指一个类确保在任何情况下都只有一个实例,并提供一个全局访问点.
属于创建型模式

单例模式重点总结

1. 私有化构造器

指无法从自身之外的类来创建对象

2. 保证线程安全和延迟加载

饿汉模式: 在类初始化的时候就创建出类的实例(线程安全),但是同时由于对象不是即用既得,会带来内存上的开销
懒汉模式: 懒汉式避免了内存上的开销,但是同时带来了多线程的并发创建的问题,正所谓**鱼和熊掌不可兼得**

  • 再通过静态工厂方法获取对象的时候要保证对象不能被重复创建,可以对静态工厂方法加Synchronized锁
  • 为了避免锁的粒度太大,造成性能损耗.一般采用Double Check Lock[双重检查锁]的方式
 public  static lazyDoubleCheckSingleton getInstance() {
        if (lazy == null) {
            synchronized (lazyDoubleCheckSingleton.class) {
                if (lazy == null) {
                    lazy = new lazyDoubleCheckSingleton();
                }
            }
        }
        return lazy;
    }

上面的Synchronized总归是要带来性能损耗的, 有没有更好的方案呢?
我既要懒加载,又要没有性能损耗
**我全都要**

  • 利用静态内部类实现单例
public class LazyInnerClassSingleton {
//虽然构造方法私有,但是,逃不过反射
    private LazyInnerClassSingleton() {
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许构建多个实例");  // 可以防止通过反射来创建实例,破坏单例
        }
    }
//LzayHolder 里面的需要等到外部方法调用才执行
    // 巧妙的利用了内部类的特性
    // JVM底层执行逻辑,完美避免了线程安全问题
    public static final LazyInnerClassSingleton getInstance() {

        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();


    }
}

为什么主类中的静态块执行,而静态内部类中的静态方法不执行呢?
因为一旦程序运行,所有涉及的类[import导入的类和内部类]都会被加载到内存中,在整个程序的运行过程中类加载只会发生一次,如果这个类没有被加载到内存之中,那么之后就不会使用这个类.
只有类初始化之后,才会调用它的静态代码块,在这里我们只有对类进行引用的时候,才会进行初始化.
这里就是利用了内部类加载的特性,完美避免了线程安全问题

3. 防止反射破坏单例

public class LazyInnerClassSingletonTest {
    public static void main(String[] args) {
        Class<?> clazz = LazyInnerClassSingleton.class;
        try {
            Constructor c = clazz.getDeclaredConstructor(null);
            c.setAccessible(true);
            Object o1 = c.newInstance();  //强制创建对象
            Object o2 = LazyInnerClassSingleton.getInstance();
            System.out.println(o1 == o2);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

输出的结果是false ,
由于调用者开了上帝模式有了金手指,通过反射创建对象,但是我们可以给他搞个障眼法,让他不能简单的就能创建成功.

在构造方法加个限制,创建多个对象直接跑出异常

	 private LazyInnerClassSingleton() {
        if(LazyHolder.LAZY != null){
            throw new RuntimeException("不允许构建多个实例");  // 可以防止通过反射来创建实例,破坏单例
        }
    }

4. 防止序列化破坏单例

当我们将一个对象创建好,有时候需要将对象序列化之后存入磁盘,下次使用时候再从磁盘中读取对象,反序列化为内存对象.
反序列化后的对象会重新分配内存.如果序列化的对象为单例对象,就违背了单例对象的初衷,相当于破坏了单例.
public class SeriableSingleton implements Serializable {
    //序列化就是说把内存中的状态通过转换成字节码的形式
//从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO)
//内存中状态给永久保存下来了
//反序列化
//讲已经持久化的字节码内容,转换为 IO 流
//通过 IO 流的读取,进而将读取的内容转换为 Java 对象
//在转换过程中会重新创建对象 new
    public final static SeriableSingleton INSTANCE = new SeriableSingleton();
    private SeriableSingleton(){}
    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
}
  public static void main(String[] args) {
        SeriableSingleton s1 = null;
        SeriableSingleton s2 = SeriableSingleton.getInstance();
        FileOutputStream fos = null; // 输出流
        try {
            //将对象序列化并写入到文件中
            fos = new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton) ois.readObject();
            ois.close();
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1 == s2);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

结果: 面试装X之--单例模式深度探索(玩转面试官)
很明显单例被破坏

问题来了,序列化怎么破坏单例的?如何避免这种破坏?

接下来就是本文的重头戏了

深入源码抽丝剥茧

  1. 从这开始读取对象
    面试装X之--单例模式深度探索(玩转面试官)
    面试装X之--单例模式深度探索(玩转面试官)
    综上所述 :
    我们只能重写一个叫做ReadREsolve()的方法,并返回单例对象来避免单例被破坏.
//重写readResolve方法,只不过覆盖了反序列化出来的对象
//还是创建了两次,发生在JVM层面,相对来说比较安全
//之前反序列化出来的对象会被GC回收	
 public SeriableSingleton ReadResolve(){
        return INSTANCE;
    }

注册式单例(未完待续)