Java设计模式-单例模式-反射和枚举相关学习

饿汉式单例

Hungry.java

/**
 * @Description 饿汉式单例
 */
public class Hungry {
    
    
    // 私有构造方法,不准其他类使用new创建
    private Hungry() {
    
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
    
    
        return hungry;
    }
}

存在的问题:如果Hungry类中存在大量的初始化内容会造成空间浪费,示例如下:

public class Hungry {
    
    

    // data1、data2、data3 此处是指存在大量的初始化内容,使用饿汉式单例的话会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];

    // 私有构造方法,不准其他类使用new创建
    private Hungry() {
    
    
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
    
    
        return hungry;
    }

}

懒汉式单例

单线程的简单单例模式

注:单线程情况下安全,多线程情况下会出现问题。

/**
 * @Description 懒汉式单例
 */
public class LazyMan {
    
    
	// 私有构造方法,不准其他类使用new创建
    private LazyMan() {
    
    
        System.out.println(Thread.currentThread().getName());
    }
    private static LazyMan lazyMan;
    
    public static LazyMan getInstance() {
    
    
        if (lazyMan == null) {
    
    
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

    public static void main(String[] args) {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
                getInstance();
            }).start();
        }
    }

}

输出信息:(多线程情况运行处的结果不同)
Thread-0
Thread-2
Thread-1
DCL单例模式 (双重检测锁模式的单例模式)

​ 上述单线程代码getInstance()创建的单例,单线程情况下线程安全,多线程情况下会存在创建多个。可以加锁解决这种问题。

代码如下:

/**
 * @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
 */
public class LazyMan {
    
    
    // 私有构造方法,不准其他类使用new创建
    private LazyMan() {
    
    
    }

    // volatile保证可见性和禁止指令重排
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance() {
    
    
        if(lazyMan == null){
    
    
            synchronized(LazyMan.class){
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan();// 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }
}

注:实例对象需要使用volatile修饰,保证可见性和禁止指令重排。

LazyMan lazyMan = new LazyMan();
步骤拆分如下:
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间

指令重排指:
执行顺序可能是123也可能是132,
如果A线程执行132步骤的时候,
B线程进入getInstance()方法会直接返回lazyMan(此时的lazyMan可能还没有完成构建)

所以此处的实例对象需要使用volatile修饰,保证可见性和禁止指令重排。

这种方式存在的问题,可以用反射来破坏单例模式

public static void main(String[] args) throws Exception {
    
    
    LazyMan lazyMan1 = getInstance();
    // 反射方式破坏单例模式
    Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
    constructor.setAccessible(true);
    LazyMan lazyMan2 = constructor.newInstance();

    System.out.println(lazyMan1);
    System.out.println(lazyMan2);
}
输出结果:
com.xiangty.single.LazyMan@1540e19d
com.xiangty.single.LazyMan@677327b6

可以使用一个私有标识来避免上述的反射造成的问题,代码如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/**
 * @Description 懒汉式单例模式-DCL单例模式(双重检测锁模式单例模式)
 *          添加一个如下的flag标识来避免反射。  (还是会存在问题,反射可以修改flag的值)
 */
public class LazyMan3 {
    
    

    private static boolean flag = false;

    // volatile保证可见性和禁止指令重排
    private volatile static LazyMan3 lazyMan;

    // 私有构造方法,不准其他类使用new创建
    private LazyMan3() {
    
    
        if(flag){
    
    
            throw new RuntimeException("对象已存在");
        } else {
    
    
            flag = true;
        }
    }

    public static LazyMan3 getInstance() {
    
    
        if (lazyMan == null) {
    
    
            synchronized (LazyMan3.class) {
    
    
                if (lazyMan == null) {
    
    
                    lazyMan = new LazyMan3(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }


    public static void main(String[] args) throws Exception {
    
    
//        verification();
        editProperties();
    }

    /**
     * 不修改flag的,验证方法,会出现异常
     *
     * @throws Exception
     */
    public static void verification() throws Exception {
    
    
//        LazyMan3 lazyMan1 = getInstance();
        Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazyMan3 lazyMan2 = constructor.newInstance();
//        LazyMan3 lazyMan1 = constructor.newInstance();
        LazyMan3 lazyMan1 = getInstance();

        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

    /**
     * 修改flag属性值的破坏方法,不会出现异常,会声明两对象
     *
     * @throws Exception
     */
    public static void editProperties() throws Exception {
    
    
        Field field = LazyMan3.class.getDeclaredField("flag");
        field.setAccessible(true);

        Constructor<LazyMan3> constructor = LazyMan3.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazyMan3 lazyMan2 = constructor.newInstance();

        // flag重设值
        field.set(lazyMan2, false);

        LazyMan3 lazyMan1 = constructor.newInstance();
        System.out.println(lazyMan1);
        System.out.println(lazyMan2);
    }

}
不能使用反射破坏枚举

普通的类都可以通过反射破坏单例模式。查看Constructor.java源码发现 不能使用反射破坏枚举。

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
    
    
        if (!override) {
    
    
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    
    
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        // 不能使用反射破坏枚举
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
    
    
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

定义一个枚举类EnumSingle.java

public enum EnumSingle {
    
    
    INSTANCE;

    public EnumSingle getInstance() {
    
    
        return INSTANCE;
    }
}

使用反射方式的测试类一

public class NoParametersTestEnumSingle{
    
    
    /**
     * 无参的构造方法反射
     * 异常信息:Exception in thread "main" java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle.<init>()
     */
    public static void main(String[] args) throws Exception {
    
    
        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        // 没有无参的构造方法
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        EnumSingle enumSingle2 = constructor.newInstance();

        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
    }
}

使用反射中无参的构造方法,提示的异常信息:Exception in thread “main” java.lang.NoSuchMethodException: com.xiangty.single.EnumSingle.() ,没有无参的构造方法。这个信息并不是源码中提及的"Cannot reflectively create enum objects"。

查看IDEA中的EnumSingle.class的编译文件如下:

public enum EnumSingle {
    
    
    INSTANCE;

    private EnumSingle() {
    
    
    }

    public EnumSingle getInstance() {
    
    
        return INSTANCE;
    }
}

编译文件中有无参的构造方法,那为什么我们使用反射无参构造的时候会出异常呢?此处需要借用jad.exe反编译工具来查看EnumSingle.java底层的实现是怎么样的。

在这里插入图片描述

使用jad.exe软件反编译EnumSingle.class然后可以得到EnumSingle.java内容如下:

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

public final class EnumSingle extends Enum
{
    
    

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

    public static EnumSingle valueOf(String name)
    {
    
    
        return (EnumSingle)Enum.valueOf(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
        });
    }
}

我们发现定义的枚举类没有无参的构造方法,存在有参构造方法,方法如下。

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

编写有参构造反射方式的方法,代码如下:

public class HavaParametersTest{
    
    
/**
     * 有参的构造方法反射
     * 异常信息:Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
     */
    public static void main(String[] args) throws Exception {
    
    
        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        // 没有无参的构造方法
        Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumSingle enumSingle2 = constructor.newInstance();

        System.out.println(enumSingle1);
        System.out.println(enumSingle2);
    }
}

使用有参构造反射的方式,就可以到了与源码一样的异常信息:Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects。

猜你喜欢

转载自blog.csdn.net/qq_33369215/article/details/105876458