单例模式分析

六月ゝ 毕业季﹏ 提交于 2020-01-07 19:22:11

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

单例模式,这个搞java的应该都懂,对于那些有缺陷的单例模式就不说了,太low,这里说下相对略叼的两种。

一、双重校验锁

先上代码:

package com.ayo.singleton;

import java.io.*;

/**
 * 双重校验锁单例模式
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {}

    public static LanhanSingleton getInstance(){
        if(INSTANCE == null){
            synchronized (LanhanSingleton.class){
                if (INSTANCE == null){
                    INSTANCE = new LanhanSingleton();
                }
            }
        }
        return INSTANCE;
    }
}

双重校验锁基本上算是比较完善的单例模式了(大部分场景下),但是它还是有缺点的,因为如果按照这么写并不能真正的保证单例,最简单的就是用反射搞你:

package com.ayo.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 双重校验锁单例模式
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {}

    public static LanhanSingleton getInstance(){
        if(INSTANCE == null){
            synchronized (LanhanSingleton.class){
                if (INSTANCE == null){
                    INSTANCE = new LanhanSingleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) throws Exception {

        /**
         * 反射获取对象
         */
        LanhanSingleton instance = LanhanSingleton.getInstance();
        //获取类的构造器
        Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
        lanhanSingletonConstructor.setAccessible(true);
        LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
        System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (instance == lanhanSingleton1));
    }

}

这个很好防止,因为类是你自己定义的嘛

package com.ayo.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 双重校验锁单例模式
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {
        //阻止反射
        if (INSTANCE != null && this != INSTANCE){
            throw new SecurityException("反射你妹啊! ");
        }
    }

    public static LanhanSingleton getInstance(){
        if(INSTANCE == null){
            synchronized (LanhanSingleton.class){
                if (INSTANCE == null){
                    INSTANCE = new LanhanSingleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) throws Exception {
        LanhanSingleton instance = LanhanSingleton.getInstance();
        /**
         * 反射获取对象
         */
        //获取类的构造器
        Constructor<LanhanSingleton> lanhanSingletonConstructor = LanhanSingleton.class.getDeclaredConstructor();
        lanhanSingletonConstructor.setAccessible(true);
        LanhanSingleton lanhanSingleton1 = lanhanSingletonConstructor.newInstance();
        System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (instance == lanhanSingleton1));
    }

}

反射很容易禁止,但是还有一种方法,就是反序列化,这种方式也可以生成一个新的对象,看下

package com.ayo.singleton;

import java.io.*;

/**
 * 双重校验锁单例模式
 *
 * @Authror ayo
 * @Date 2020/1/7 14:33
 */
public class LanhanSingleton implements Serializable {

    private volatile static LanhanSingleton INSTANCE;

    private LanhanSingleton() {
        //阻止反射
        if (INSTANCE != null && this != INSTANCE){
            throw new SecurityException("反射你妹啊! ");
        }
    }

    public static LanhanSingleton getInstance(){
        if(INSTANCE == null){
            synchronized (LanhanSingleton.class){
                if (INSTANCE == null){
                    INSTANCE = new LanhanSingleton();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) throws Exception {
        /**
         * 序列化
         */
        //获得输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("lanhanFile"));
        //将单例对象序列化到磁盘
        oos.writeObject(LanhanSingleton.getInstance());
        //获取输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("lanhanFile"));
        //从磁盘读取被序列化的单例对象
        LanhanSingleton lanhanSingleton = (LanhanSingleton) ois.readObject();
        //判断两者是否相等
        System.out.println("反序列化后的对象和原对象是否相等? = " + (lanhanSingleton == LanhanSingleton.getInstance()));
    }

}

感觉是不是有点懵比,为什么反序列化后对象变了,跟进去看看(打上你的断点debug)

跟源码的时候会看到主要经过了以下几个方法:

ok,当你跟到这里时发现调用了反射(利用了ObjectStreamClass中的构造器,所以是不是你在构造器中加校验没啥卵用?不是的,这种构造器程序员调不了的,但是你禁止不了,因为反序列化时会调啊,一样会出问题,这怎么办?),我知道你又懵逼了,别着急,继续往下看源码

仔细调试下这段逻辑,都打上断点,最后你会发现他进入了hasReadResolveMethod方法,

从而导致表达式desc.hasReadResolveMethod()为false,继续分析发现这个readResolveMethod其实是反射获取desc(com.ayo.singleton.LanhanSingleton: static final long serialVersionUID = -6555766677206059999L;)中的readResolve方法,如果这个方法不为空,返回的对象其实是这个方法的返回值,增加一个方法

private Object readResolve(){
        return INSTANCE;
    }

搞定了。

二、枚举

直接上代码了(累了,看源码真的看的眼疼)

package com.ayo.singleton;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

/**
 * 基于枚举的单例模式
 *
 * @Authror ayo
 * @Date 2020/1/7 14:53
 */
public enum EnumSingleton {

    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) throws Exception {
        /**
         * 序列化
         */
        //获得输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumFile"));
        //将单例对象序列化到磁盘
        oos.writeObject(INSTANCE);
        //获取输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumFile"));
        //从磁盘读取被序列化的单例对象
        EnumSingleton enumSingleton = (EnumSingleton) ois.readObject();
        //判断两者是否相等
        System.out.println("反序列化后的对象和原对象是否相等? = " + (INSTANCE == enumSingleton));

        Thread.sleep(1000);

        /**
         * 反射获取对象
         */
        //获取类的构造器
        Constructor<EnumSingleton> enumSingletonConstructor = EnumSingleton.class.getDeclaredConstructor();
        enumSingletonConstructor.setAccessible(true);
        EnumSingleton enumSingletonReflect = enumSingletonConstructor.newInstance();
        System.out.println("通过反射获取到的对象和单例对象是否相同? = " + (INSTANCE == enumSingletonReflect));
    }
}

这时候你会发现枚举单例的好处了,两种问题都避免了,为什么?

先说反序列化,

之前的步骤和普通对象都一样,但是这一步你会发现枚举走的是TC_ENUM,分析readEnum方法,看到valueof方法了吧

然后是反射,

这更简单了,其实就是不准反射枚举呗,通过源码分析可以知道其实枚举类是被特殊对待了,所以天然的支持单例模式。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!