面试题02-玩转单例模式

单例模式

1、什么叫单例模式

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意点:

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。
  • 不需要实例化该类对象就可以访问

2、单例实现介绍

**实现意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

**主要解决:**一个全局使用的类频繁地创建与销毁。

**何时使用:**当您想控制实例数目,节省系统资源的时候。

**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

**关键代码:**构造函数是私有的。

3、具体实现

3.1、饿汉式单例

3.1.1、代码实现
/**
 * @author luosong
 * @version 1.0
 * @date 2021/3/23 9:17
 * 饿汉式单例
 */
public class HungrySingle {
    // 构造方法私有化
    private HungrySingle(){

    }
	// 单例自己创建自已的对象
    private static HungrySingle hungrySingle = new HungrySingle();
	// 对外提供唯一的访问方式
    public static HungrySingle getInstance(){
        return hungrySingle;
    }
}
3.1.2、存在的问题

类加载的时候就去初始化类,如果对象并没有使用,浪费内存,增加服务器负担。

3.2、懒汉式单例

3.2.1、代码实现
/**
 * @author luosong
 * @version 1.0
 * @date 2021/3/23 9:16
 * 懒汉式单例
 */
public class LazySingle {
    private LazySingle(){

    }

    public static LazySingle lazySingle = null;

    public static LazySingle getInstance(){
        if (lazySingle == null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}
3.2.2、存在的问题

懒汉式单例在类加载并不会创建,在使用调用getInstance()方法才会创建,但是存在线程安全的问题,多线程情况下可能会出现创建多个单例的情况

测试代码如下:

public class SingleTest {
    public static void main(String[] args) {
        for (int i = 0; i <20 ; i++) {
            new Thread(()->{
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(LazySingle.getLazySingle().hashCode());
                //System.out.println(HungrySingle.getHungrySingle().hashCode());
            }).start();
        }
    }
}

测试结果如下:在并发情况下懒汉模式并不安全,出现了不同hashCode值的对象,说明并没有保持单例模式的特性。

在这里插入图片描述

3.3、懒汉式单例synchronized

基于懒汉式存在的并发问题,加锁解决

3.3.1、代码实现
public class LazySingle {
    private LazySingle(){

    }

    public static LazySingle lazySingle = null;

    public static synchronized LazySingle getLazySingle(){
        if (lazySingle == null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}
3.3.2、存在的问题

该单例模式确实可以解决并发的问题,但是synchronized是重量级锁,效率极低不推荐使用。

3.4、懒汉式单例DCL

基于上诉synchronized加锁造成的效率低下问题,提出双重锁的机制,同样有加锁的效果,但是效率高。

3.4.1、代码实现
public class LazySingle {
    private LazySingle(){

    }

    public static LazySingle lazySingle = null;

    public static synchronized LazySingle getLazySingle(){
        if (lazySingle == null){
            // 只要lazySingle为空就锁住该类,只允许当前线程操作
            synchronized (LazySingle.class){
                if (lazySingle == null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
}
3.4.2、存在的问题

DCL双重锁,效率比synchronized有较大的提高,但是内部实现还是基于synchronized锁去实现,所以也不太可取。

3.5、懒汉式单例静态内部类

3.5.1、代码实现

这种方式利用了classloader机制来保证初始化LAZYSINGLE 时只有一个线程,LazySingleStaticClass类被装载了,LAZYSINGLE不一定被初始化。因为SingletonHolder类没有被主动使用,只有通过显式调用 getInstance方法时,才会显式装载LazySingleStaticClass类,从而实例化 LAZYSINGLE

public class LazySingleStaticClass {
    private LazySingleStaticClass(){

    }

    private static class SingletonHolder{
        private static LazySingleStaticClass LAZYSINGLE = new LazySingleStaticClass();
    }

    public static LazySingleStaticClass getInstance(){
        return SingletonHolder.LAZYSINGLE;
    }
}

4、破坏单例

4.1、指令重排volatile

创建对象的三个步骤

  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间

在多线程情况下可能会出现指令重排情况本来顺序执行是123,可能因为指令重排执行顺序会变成132针对上诉问题,lazySingle应该加上关键字volatile

public static volatile LazySingle lazySingle = null;

4.2、反射

4.2.1、代码实现

根据反射来破坏单例,引用DCL双重锁单例测试

public class LazySingle {
    private LazySingle(){

    }

    public static volatile LazySingle lazySingle = null;

    public static synchronized LazySingle getLazySingle(){
        if (lazySingle == null){
            synchronized (LazySingle.class){
                if (lazySingle == null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
}

反射破坏单例代码

public class ReflectionTest {
    // 两个对象返回的结果果然不一样,因为反射绕开了getLazySingle方法走了私有的构造方法
    public static void main(String[] args) throws Exception {
        LazySingle lazySingle = LazySingle.getLazySingle();

        Class<? extends LazySingle> aClass = lazySingle.getClass();
        Constructor<? extends LazySingle> declaredConstructor = aClass.getDeclaredConstructor(null);
        // 私有方法开启安全模式
        declaredConstructor.setAccessible(true);
        LazySingle lazySingle1 = declaredConstructor.newInstance(null);
        System.out.println(lazySingle==lazySingle1);// false
    }
}
4.2.2、解决办法
public class LazySingle {
    // flag 必须是static的,不然每个对象都会有一份,构造里面的判断逻辑没什么用
    private static Boolean flag = true;

    private LazySingle(){
        synchronized (LazySingle.class){
            if (flag){
                // 第一次通过getLazySingle()进来,后续直接报错
                flag = false;
            }else {
                throw new RuntimeException("不要试图用反射破坏单例");
            }
        }
    }

    public static volatile LazySingle lazySingle = null;

    public static synchronized LazySingle getLazySingle(){
        if (lazySingle == null){
            synchronized (LazySingle.class){
                if (lazySingle == null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
}

但是有问题存在,反射难道不能破坏属性吗?

单例继续破坏

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        Class<? extends LazySingle> aClass1 = LazySingle.class;
        Constructor<? extends LazySingle> declaredConstructor = aClass1.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazySingle lazySingle = declaredConstructor.newInstance(null);
        System.out.println(lazySingle);

        Class<? extends LazySingle> aClass2 = LazySingle.class;
        Constructor<? extends LazySingle> declaredConstructor1 = aClass2.getDeclaredConstructor(null);
        declaredConstructor1.setAccessible(true);
        Field flag = aClass2.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(aClass2,true);
        LazySingle lazySingle1 = declaredConstructor1.newInstance(null);
        System.out.println(lazySingle1);
    }
}

执行结果,明显两个值不相等。

com.test.LazySingle@6f75e721
com.test.LazySingle@782830e

那又该如何解决呢?反射初始化方法newInstance源码给出如下提示

在这里插入图片描述

4.2.3、枚举防止反射破坏

新建枚举类EnumSingle

public enum EnumSingle {
    INSTANCE;
    
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}

编译完枚举类可以直接反编译看出,枚举类其实就是一个Java类

在这里插入图片描述

为了测试上诉情况,直接使用反射创建枚举,看是否为出现反射定义的枚举错误

先通过idea自带反编译工具查看枚举类型的构造函数,如下可以看出是无参构造

在这里插入图片描述

class Test{
    public static void main(String[] args) throws Exception {
        Class<EnumSingle> enumSingleClass = EnumSingle.class;
        Constructor<EnumSingle> declaredConstructor = enumSingleClass.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance(null);
        System.out.println(enumSingle);
    }
}

执行结果如下

在这里插入图片描述

上诉报错原因是构造方法出错,但明明idea反编译工具显示的是无参构造,为什么会报这样的错呢?

重新使用市面上流行的jad反编译工具在cmd窗口执行jad -sjava EnumSingle.class,结果如下

在这里插入图片描述

public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/test/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

通过jad编译后的文件直接可以看出,此枚举类型的构造方法并非无参构造,而是private EnumSingle(String s, int i)

修改代码如下

class Test{
    public static void main(String[] args) throws Exception {
        Class<EnumSingle> enumSingleClass = EnumSingle.class;
        // private EnumSingle(String s, int i)
        Constructor<EnumSingle> declaredConstructor = enumSingleClass.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance(null);
        System.out.println(enumSingle);
    }
}

执行结果,达到目的

在这里插入图片描述

4.3、序列化

4.3.1、代码实现
public class SerializableSingle {
    public static void main(String[] args) throws Exception {
        // Write to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testFile"));
        oos.writeObject(LazySingle.getLazySingle());
        // Read from file
        File file = new File("testFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        LazySingle newInstance = (LazySingle) ois.readObject();
        System.out.println(newInstance == LazySingle.getLazySingle());
    }
}
4.3.2、源码分析

1、因为存在不同的对象,那么从如下代码开始分析

LazySingle newInstance = (LazySingle) ois.readObject();

2、进入源码readObject方法

public final Object readObject()
        throws IOException, ClassNotFoundException{
        if (enableOverride) {
            return readObjectOverride();
        }
        // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            // 生成对象的方法
            Object obj = readObject0(false);
            // 此处省略部分代码
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
    }

3、进入方法readObject0

private Object readObject0(boolean unshared) throws IOException {
        // 此处省略代码
        byte tc;
        while ((tc = bin.peekByte()) == TC_RESET) {
            bin.readByte();
            handleReset();
        }

        depth++;
        totalObjectRefs++;
        try {
            switch (tc) {
                // 此处省略代码
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

4、进入方法readOrdinaryObject()

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        // 此处省略代码
        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
            // 如果实例化对象有readResolve方法,hasReadResolveMethod返回true
            // 此方法体主要是取出readResolve方法的值rep将obj引用指向readResolve方法的返回值rep
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

从上面源码可知,实例化对象需要存在一个readResolve方法即可,但是怎么确定参数和返回值呢?

由ObjectStreamClass类构造方法可以得出

readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);

private static Method getInheritableMethod(Class<?> cl, String name,
                                               Class<?>[] argTypes,
                                               Class<?> returnType){}

综上实例化对象加入方法readResolve如下所示。

// 该方法并非被重写的方法
private Object readResolve(){
    return lazySingle;
}

运行代码,返回true成功。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值