JUC并发编程(十)--Volatile、原子性以及单例模式的应用

一、JMM

1、什么是JMM

JMM是一种Java内存模型,是一种概念性的约定,而不是实际存在的东西。

2、JMM的约定

  • 线程解锁前,必须把变量立即刷回主存;
    我们知道,一个线程工作时,会将主存中的变量复制一份给线程自己的内存,作为一个副本,线程对此变量的操作,都是在副本上的操作,所以当线程运行完毕,解锁的时候,必须将副本的值同步回主存。

  • 线程加锁前,必须读取主存中最新的变量值,然后复制到自己的工作内存中;

  • 加锁和解锁是同一把锁。

3、八种操作

在这里插入图片描述

  • 从主存中read操作和load到线程工作内存中,是一组操作;
  • 线程的执行引擎使用此变量,并在使用之后返回此变量,也是一组操作;
  • 从工作内存中保存此变量(store操作),并写回主存,也是一组操作;
  • 加锁(lock)和解锁(unlock),也是一组操作。
    在这里插入图片描述
    这里有个问题,现在线程A和线程B都从主存中读取了Flag,然后线程B先修改了值,并写回了内存,这时线程A不能及时得到此消息,即不能及时可见此变量。所以这时我们就需要Volatile了。

二、Volatile

1、可见性

先上代码:

package com.zhan.juc.volatiletest;

import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2021/1/28 21:07
 */
public class JMMDemo {

    private static int num = 0;

    public static void main(String[] args){
        new Thread(() -> {
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

我们看上面的代码,可以看到,有一个线程,里面while循环,不停的判断num的值,然后在线程的下面将num值赋为1,那么这时我们开出来的这个线程会停掉吗?
看结果:
在这里插入图片描述
我们发现,并没有停,这是为什么呢?就像上面说的那样,线程将num的值从主存中复制了一份,然后主存中num值的变动,线程并没有看到。
那么要如何解决?接下来看:

package com.zhan.juc.volatiletest;

import java.util.concurrent.TimeUnit;

/**
 * @Author Zhanzhan
 * @Date 2021/1/28 21:07
 */
public class JMMDemo {

    private volatile static int num = 0;

    public static void main(String[] args){
        new Thread(() -> {
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        num = 1;
        System.out.println(num);
    }
}

我们看到,上面的代码中num用了volatile来修饰,然后看结果:
在这里插入图片描述
我们发现,结果符合我们的预期,线程看到了主存中num的值的变动。

2、不保证原子性

什么是原子性:不可分割,线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。
废话不多说,上代码:

package com.zhan.juc.volatiletest;

/**
 * 测试volatile不保证原子性
 *
 * @Author Zhanzhan
 * @Date 2021/1/28 21:19
 */
public class Demo {
    private volatile static int num = 0;

    public static void add() {
        num++;
    }

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

        // 判断当前的线程是否大于两条
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " num=" + num);
    }
}

如果线程能保证原子性,那么输出的结果应该是num=20000,现在我们看结果:
在这里插入图片描述
发现,volatile不能保证原子性。
那这时我们就有一个问题,如果不使用lock和synchronized,怎么保证原子性?

原子类

上代码:

package com.zhan.juc.volatiletest;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 测试volatile不保证原子性
 *
 * @Author Zhanzhan
 * @Date 2021/1/28 21:19
 */
public class Demo {
//    private volatile static int num = 0;

    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add() {
//        num++; // 不是一个原子性操作
        num.getAndIncrement();
    }

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

        // 判断当前的线程是否大于两条
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " num=" + num);
    }
}

看结果:
在这里插入图片描述

3、禁止指令重排

什么是指令重排:计算机并不是按照我们写的程序的顺序那样去执行的,会进行指令优化。
源代码 =》编译器优化的重排=》指令并行也可能的重排=》内存系统的重排=》执行

处理器在进行指令重排的时候,会考虑数据之间的依赖性

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

我们期望的顺序:1234
但可能编程的执行顺序:2134   1324

举例说明,指令重排可能引发的问题:
假设有两个线程A和B,然后有四个变量:a b x y
a b x y四个值默认为0

线程A线程B
x = ay = b
b = 1a = 2

线程A执行了两个操作:x = a 和 b = 1;
线程B执行了两个操作:y = b 和 a = 2;
在线程A执行 x = a后,线程B执行了 y = b,
这时我们预期的结果是 x = 0 和 y = 0;
但是现在线程A中的变量没有依赖关系,线程B中的变量也没有依赖关系,所以线程A和线程B就有可能发生指令重排,会造成如下操作:

线程A线程B
b = 1a = 2
x = ay = b

这时,得到的结果就为 x = 2 和 y = 1;

那么如何解决呢?
我们可以用volatile修饰,来避免指令重排,volatile会加一个内存屏障,来保证特定操作的执行顺序以及内存可见性。
在这里插入图片描述

三、单例模式

1、常见的懒汉模式

这里我们跳过饿汉模式,直接上懒汉模式:

package com.zhan.juc.volatiletest.single;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 无检测的单例模式
     *
     * @return
     */
    public static LazyMan getInstanceByNotSafe() {
        if (lazyMan == null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    
    public static void main(String[] args) {
        /**
         * 测试多线程下,这种单例模式是否安全
         */
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                LazyMan.getInstanceByNotSafe();
            }).start();
        }
    }
}

这样写对吗?看结果:
在这里插入图片描述
我们发现不是单例的,那么一般的懒汉模式要怎么写呢?

package com.zhan.juc.volatiletest.single;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static LazyMan lazyMan;

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                }
            }
        }
        return lazyMan;
    }

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

看结果:
在这里插入图片描述
符合预期,是单例的。但是这里会有一个问题,new 单例对象的这个操作,不是一个原子操作。即 lazyMan = new LazyMan(); // 不是一个原子性操作
这一步经过了那几个操作:

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

我们正常认为的执行顺序是 1=》2=》3
但是有可能会指令重排,执行的顺序是 1=》3=》2
如果此时有A线程执行的顺序是1=》3=》2,那么执行到3这一步时,有个B线程也进来了,就会发现lazyMan 这个对象不为null,于是直接返回,但是此时lazyMan 还没完成构造,就会引发问题,所以,就要使用volatile来修饰 lazyMan ,禁止指令重排。于是代码如下:

package com.zhan.juc.volatiletest.single;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

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

2、破解一般的懒汉模式

我们一般就认为,上面的那种单例模式,没啥毛病了。
但是!依然可以有方法破解上述的单例模式,怎么破解呢?用反射!
上代码:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解单例模式
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器
        declaredConstructor.setAccessible(true); // 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通过反射创建对象

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
我们看到,这两个对象实例的hashCode不一样,是两个不同的对象实例,于是,我们得出结论,一般的懒汉模式就这样被反射破解了,那么有应对方法吗?既然是获取私有构造器,来创建实例,那么我们在私有构造器上也加上校验呢?来看:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解单例模式
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器
        declaredConstructor.setAccessible(true); // 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通过反射创建对象

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
我们发现,这种方法被抵挡了。那么,还有可能破解这种单例模式吗?

我们思考下,刚才那种破解,第一个对象实例是通过单例模式来创建的,第二个对象实例是通过反射来创建的,那么如果两个对象都通过反射来创建呢?上代码:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解单例模式
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器
        declaredConstructor.setAccessible(true); // 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通过反射创建对象
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
非常的amazing啊,竟然又被破解了,既然这样,我就较上劲儿了,还有什么方法能抵挡吗?有的,我们用下红绿灯模式,就是通过设置一个标识,来再次进行校验,上代码:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static boolean flag = false;

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (!flag){
                flag = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏单例");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解单例模式
//        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器
        declaredConstructor.setAccessible(true); // 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通过反射创建对象
        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
符合预期,抵挡了这种方法,那到现在了,还有可能破解单例吗?有的,就是,如果我知道了你设置的标识符,我直接对标识符进行修改呢?

package com.zhan.juc.volatiletest.single;

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

/**
 * 单例模式--懒汉
 *
 * @Author Zhanzhan
 * @Date 2021/1/30 13:39
 */
public class LazyMan {

    private static boolean flag = false;

    private volatile static LazyMan lazyMan; // 使用volatile修饰,禁止指令重排

    private LazyMan() {
        synchronized (LazyMan.class){
            if (!flag){
                flag = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏单例");
            }
        }
        System.out.println(Thread.currentThread().getName() + "ok");
    }

    /**
     * 双重检测锁 的 懒汉单例模式  DCL懒汉式
     * @return
     */
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    /**
                     * 1、分配内存空间;
                     * 2、执行构造方法,初始化对象;
                     * 3、把这个对象指向这个空间。
                     */
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws Exception {
        // 用反射破解单例模式
//        LazyMan instance = LazyMan.getInstance();

        Field flag = LazyMan.class.getDeclaredField("flag");// 假设我们知道了标识符的名称
        flag.setAccessible(true); // 无视私有权限

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取构造器
        declaredConstructor.setAccessible(true); // 无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance(); // 通过反射创建对象

        // 更改标识符
        flag.set(instance2, false);

        LazyMan instance = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
我们发现,又被破坏了。那么我们如何解决呢?看反射中newInstance()的源码:

/**
     * Uses the constructor represented by this {@code Constructor} object to
     * create and initialize a new instance of the constructor's
     * declaring class, with the specified initialization parameters.
     * Individual parameters are automatically unwrapped to match
     * primitive formal parameters, and both primitive and reference
     * parameters are subject to method invocation conversions as necessary.
     *
     * <p>If the number of formal parameters required by the underlying constructor
     * is 0, the supplied {@code initargs} array may be of length 0 or null.
     *
     * <p>If the constructor's declaring class is an inner class in a
     * non-static context, the first argument to the constructor needs
     * to be the enclosing instance; see section 15.9.3 of
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * <p>If the required access and argument checks succeed and the
     * instantiation will proceed, the constructor's declaring class
     * is initialized if it has not already been initialized.
     *
     * <p>If the constructor completes normally, returns the newly
     * created and initialized instance.
     *
     * @param initargs array of objects to be passed as arguments to
     * the constructor call; values of primitive types are wrapped in
     * a wrapper object of the appropriate type (e.g. a {@code float}
     * in a {@link java.lang.Float Float})
     *
     * @return a new object created by calling the constructor
     * this object represents
     *
     * @exception IllegalAccessException    if this {@code Constructor} object
     *              is enforcing Java language access control and the underlying
     *              constructor is inaccessible.
     * @exception IllegalArgumentException  if the number of actual
     *              and formal parameters differ; if an unwrapping
     *              conversion for primitive arguments fails; or if,
     *              after possible unwrapping, a parameter value
     *              cannot be converted to the corresponding formal
     *              parameter type by a method invocation conversion; if
     *              this constructor pertains to an enum type.
     * @exception InstantiationException    if the class that declares the
     *              underlying constructor represents an abstract class.
     * @exception InvocationTargetException if the underlying constructor
     *              throws an exception.
     * @exception ExceptionInInitializerError if the initialization provoked
     *              by this method fails.
     */
    @CallerSensitive
    @ForceInline // to ensure Reflection.getCallerClass optimization
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, clazz, 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;
    }

我们看到:
在这里插入图片描述

3、使用枚举实现单例模式

上代码:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 枚举实现单例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public enum  EnumSingle {

    INSTANCE("张三", 3);

    // 这里还可以定义几个类的属性;
    private String name;
    private int age;

    EnumSingle(String name, int age) {
    }

    /**
     * 可以定义一些方法
     */
    public void anyMethod(){

    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {

        EnumSingle instance1 = EnumSingle.INSTANCE;
        EnumSingle instance2 = EnumSingle.INSTANCE;
        System.out.println(instance1 == instance2);
    }
}


看结果:
在这里插入图片描述

符合预期,是同一个对象。
看反射是否能破坏此单例模式:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 枚举实现单例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public enum  EnumSingle {

    INSTANCE("张三", 3);

    // 这里还可以定义几个类的属性;
    private String name;
    private int age;

    EnumSingle(String name, int age) {
    }

    /**
     * 可以定义一些方法
     */
    public void anyMethod(){

    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);

        EnumSingle instance1 = declaredConstructor.newInstance();
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}


看结果:
在这里插入图片描述
咦,我们发现这里竟然报没找到对应的方法,那也就是说我们这里没找到对应的构造器,那我们就打印下这个枚举类有哪些构造器。上代码:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 枚举实现单例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public enum  EnumSingle {

    INSTANCE("张三", 3);

    // 这里还可以定义几个类的属性;
    private String name;
    private int age;

    EnumSingle(String name, int age) {
    }

    /**
     * 可以定义一些方法
     */
    public void anyMethod(){

    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<?>[] declaredConstructors = EnumSingle.class.getDeclaredConstructors();
        for (Constructor cs : declaredConstructors){
            System.out.println(cs);
        }
    }
}

看结果:
在这里插入图片描述
原来,这里的构造器里面,有四个参数,类型分别是String、int、String、int,那我们就修改下原来的代码,来看:

package com.zhan.juc.volatiletest.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 枚举实现单例模式
 * @Author Zhanzhan
 * @Date 2021/1/30 14:34
 */
public enum  EnumSingle {

    INSTANCE("张三", 3);

    // 这里还可以定义几个类的属性;
    private String name;
    private int age;

    EnumSingle(String name, int age) {
    }

    /**
     * 可以定义一些方法
     */
    public void anyMethod(){

    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class, String.class, int.class);
        declaredConstructor.setAccessible(true);

        EnumSingle instance1 = declaredConstructor.newInstance();
        EnumSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

看结果:
在这里插入图片描述
报的错误显示,不能使用反射创建枚举对象。
符合预期,所以使用枚举实现单例模式,是最安全和简单的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值