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

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

  • 一、JMM
    • 1、什么是JMM
    • 2、JMM的约定
    • 3、八种操作
  • 二、Volatile
    • 1、可见性
    • 2、不保证原子性
      • 原子类
    • 3、禁止指令重排
  • 三、单例模式
    • 1、常见的懒汉模式
    • 2、破解一般的懒汉模式
    • 3、使用枚举实现单例模式

一、JMM

1、什么是JMM

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

2、JMM的约定

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

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

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

3、八种操作

JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第1张图片

  • 从主存中read操作和load到线程工作内存中,是一组操作;
  • 线程的执行引擎使用此变量,并在使用之后返回此变量,也是一组操作;
  • 从工作内存中保存此变量(store操作),并写回主存,也是一组操作;
  • 加锁(lock)和解锁(unlock),也是一组操作。
    JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第2张图片
    这里有个问题,现在线程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,那么这时我们开出来的这个线程会停掉吗?
看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第3张图片
我们发现,并没有停,这是为什么呢?就像上面说的那样,线程将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来修饰,然后看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第4张图片
我们发现,结果符合我们的预期,线程看到了主存中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,现在我们看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第5张图片
发现,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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第6张图片

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 = a y = b
b = 1 a = 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 = 1 a = 2
x = a y = b

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

那么如何解决呢?
我们可以用volatile修饰,来避免指令重排,volatile会加一个内存屏障,来保证特定操作的执行顺序以及内存可见性。
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第7张图片

三、单例模式

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();
        }
    }
}

这样写对吗?看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第8张图片
我们发现不是单例的,那么一般的懒汉模式要怎么写呢?

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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第9张图片
我们看到,这两个对象实例的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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第10张图片
我们发现,这种方法被抵挡了。那么,还有可能破解这种单例模式吗?

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

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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第11张图片
非常的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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第12张图片
符合预期,抵挡了这种方法,那到现在了,还有可能破解单例吗?有的,就是,如果我知道了你设置的标识符,我直接对标识符进行修改呢?

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);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第13张图片
我们发现,又被破坏了。那么我们如何解决呢?看反射中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.
     *
     * 

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. * *

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 * The Java™ Language Specification. * *

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. * *

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; }

我们看到:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第14张图片

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

上代码:

package com.zhan.juc.volatiletest.single;

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

    enum EnumTest{
     
        INSTANCE;

        private EnumSingle enumSingle = null;

        private EnumTest(){
     
            enumSingle = new EnumSingle();
        }

        public EnumSingle getEnumSingle(){
     
            return enumSingle;
        }
    }


    public static void main(String[] args){
     
        EnumSingle instance1 = EnumTest.INSTANCE.getEnumSingle();
        EnumSingle instance2 = EnumTest.INSTANCE.getEnumSingle();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

看结果:
JUC并发编程(十)--Volatile、原子性以及单例模式的应用_第15张图片
符合预期,是同一个对象。
看反射是否能破坏此单例模式:

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 class EnumSingle {
     

    enum EnumTest{
     
        INSTANCE;

        private EnumSingle enumSingle = null;

        private EnumTest(){
     
            enumSingle = new EnumSingle();
        }

        public EnumSingle getEnumSingle(){
     
            return enumSingle;
        }
    }


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

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

看结果:
在这里插入图片描述
符合预期,所以使用枚举实现单例模式,是最安全和简单的。

你可能感兴趣的:(多线程,java,java,多线程,并发编程,juc)