Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)

文章目录

    • 1. 什么是单列模式
    • 2. 什么是单列类
    • 3. 为什么枚举的单例不能被反射破坏
    • 4. 单例模式的创建
      • 4.1 饿汉式单例
      • 4.2 懒汉式单例
        • 4.2.1 懒汉式单例一(多线程场景)
        • 4.2.2 懒汉式单例二 (DLC懒汉-双重检测锁模式)
        • 4.2.3 懒汉式单例三 (双重检测锁模式+volatile )
        • 4.2.4 懒汉式单例四 (静态内部类)
      • 4.3 反射-让单例不再单例(打破单例模式)
        • 4.3.1 破坏(双重检测锁模式+volatile)
          • 解决办法
        • 4.3.2 反射破坏升级
          • 解决办法
        • 4.3.3 反射破坏升级Plus
        • 4.3.4 破坏(静态内部类)
      • 4.4 枚举类的单例模式
        • 4.4.1 反射破坏失败了?

1. 什么是单列模式

  • 指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,windows的Recycle Bin(回收站),多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  • 构造器私有(最重要的思想)

2. 什么是单列类

  • 只有一个实例对象
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点;

3. 为什么枚举的单例不能被反射破坏

答案

4. 单例模式的创建

4.1 饿汉式单例

//饿汉式单列
public class HungryDemo1 {
    private HungryDemo1() {
    }

    private final static HungryDemo1 HungryDemo1 = new HungryDemo1();

    public static HungryDemo1 getInstance(){
        return HungryDemo1;
    }

}

4.2 懒汉式单例

//懒汉式单例
public class LazyManDemo1 {
    private LazyManDemo1() {
    }

    private static LazyManDemo1 LazyManDemo1;

    public static LazyManDemo1 getInstance(){
        if (LazyManDemo1 == null){
            LazyManDemo1 = new LazyManDemo1();
        }
        return LazyManDemo1;
    }
}

在单线程中,以上的懒汉式代码是不会出现问题的,但是如果在多线程的并发环境中,还能保证代码的正确性吗?让我们来试验一下:

4.2.1 懒汉式单例一(多线程场景)

//懒汉式单例(多线程场景)
public class LazyManDemo2 {
    private LazyManDemo2() {
        System.out.println(Thread.currentThread().getName()+" - > 创建了单例对象");
    }

    private static LazyManDemo2 LazyMan;
	
    public static LazyManDemo2 getInstance(){
        if (LazyMan == null){
            LazyMan = new LazyManDemo2();
        }
        return LazyMan;
    }

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

运行结果:

Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第1张图片

可以看到,在多线程模式下,LazyManDemo2的3次运行结果中,有两次都创建了多个实例对象,让单例模式不再单例。

原因:

Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第2张图片

4.2.2 懒汉式单例二 (DLC懒汉-双重检测锁模式)

//懒汉式单例 双重检测锁模式
public class LazyManDemo2 {
    private LazyManDemo2() {
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private static LazyManDemo2 LazyMan;

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

    public static void main(String[] args) {
        for (int j = 0; j < 10; j++) {
            new Thread(() -> {
                LazyManDemo2.getInstance();
            }).start();
        }
    }
}
  1. 为了防止new LazyManDemo2()被执行多次,因此在实例化操作之前加上Synchronized 同步锁,**注意:**当synchronized()作用于静态方法时,就不再用this当锁对象,因为静态属性或方法不需要new出属性访问,所以就没有this引用的存在。

  2. 进入Synchronized 临界区以后,又进行了一次判断。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象

那这样构建出的单例模式是不是就绝对安全了呢?

首先来看一下new LazyManDemo2() 底层JVM的创建过程:

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

现在模拟一个场景:A、B两个线程,A线程先访问了getInstance()方法,当A线程正在构建LazyMan对象时,B线程此时进入getInstance()方法,

Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第3张图片

在对象的创建过程中,如果A线程执行顺序是按照132执行,A线程正在执行步骤3且未完成执行状态,B线程也开始执行getInstance()方法,此时的LazyMan对象已经不在指向null,所以当B线程抢占到CPU资源,就会执行 if(instance == null)且返回结果为false, 直接拿着未初始化的LazyMan对象直接return结束掉方法,返回一个未构造完成LazyMan对象,导致问题的出现。这个问题改如何解决呢?

4.2.3 懒汉式单例三 (双重检测锁模式+volatile )

使用volatile,保证创建对象的过程是一个原子性操作,阻止了变量访问前后的指令重拍,保证指令的执行顺序,解决了DLC失效问题

private volatile static LazyManDemo2 LazyMan;

public class LazyManDemo3 {
    private LazyManDemo3() {
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private volatile static LazyManDemo3 LazyMan;

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

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

4.2.4 懒汉式单例四 (静态内部类)

//静态内部类
public class HolderSingle {
   private HolderSingle() {
       System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
   }

   public static HolderSingle getInstance(){
       return InnerClass.LazyMan;
   }

   private static class InnerClass{
       private static final HolderSingle LazyMan = new HolderSingle();
   }
}
  1. 从外部无法直接访问静态内部类InnerClass,只有当调用LazyManDemo4.getInstance()方法的时候,才能得到单例对象LazyMan。
  2. LazyMan对象初始化的时机并不是在单例类LazyManDemo4被加载的时候,而是在调用getInstance方法时,静态内部类InnerClass才会被加载。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

4.3 反射-让单例不再单例(打破单例模式)

4.3.1 破坏(双重检测锁模式+volatile)

public class DestroySingle {
    private DestroySingle() {
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private static volatile DestroySingle LazyMan;

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

    public static void main(String[] args) throws Exception {
        //通过单例获取实例
        DestroySingle instance1 = DestroySingle.getInstance();
        //通过反射获取实例
        Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        DestroySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
    }
}
 /*================= 输出结果================
    main - > 创建了单例对象
    main - > 创建了单例对象
    com.inspur.vista.ylt.conver.api.single.LazyManDemo4@77a567e1
    com.inspur.vista.ylt.conver.api.single.LazyManDemo4@736e9adb
    instance1 == instance2:false
================= 输出结果================*/
解决办法
private DestroySingle() {
        synchronized (DestroySingle.class){
            if (LazyMan!= null){
                throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
            }
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
        }
    }
 /*================= 输出结果================
    main - > 创建了单例对象
	Exception in thread "main" java.lang.reflect.InvocationTargetException
		at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
		at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
		at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
		at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
		at org.javaboy.vhr.controller.system.DestroySingle.main(DestroySingle.java:32)
	Caused by: java.lang.RuntimeException: 单例对象,拒绝反射破坏操作!!
		at org.javaboy.vhr.controller.system.DestroySingle.(DestroySingle.java:8)
		... 5 more
================= 输出结果================*/

4.3.2 反射破坏升级

在初次创建单例实例时就使用反射创建:

public class DestroySingle {
    private DestroySingle() {
        if (LazyMan!= null){
            throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
        }
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private static volatile DestroySingle LazyMan;

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

    public static void main(String[] args) throws Exception {
        Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
        //通过单例获取实例
        DestroySingle instance1 = declaredConstructor.newInstance();
        //通过反射获取实例
        declaredConstructor.setAccessible(true);
        DestroySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
    }
}
 /*================= 输出结果================
    main - > 创建了单例对象
    main - > 创建了单例对象
    org.javaboy.vhr.controller.system.DestroySingle@7cca494b
    org.javaboy.vhr.controller.system.DestroySingle@7ba4f24f
    instance1 == instance2:false
================= 输出结果================*/

从结果可以看出单例类又获取了两个实例对象,通过反射再次成功的破坏了单例模式;

解决办法

通过声明标志变量,在单例类第一次实例化时就进行标志更改

public class DestroySingle {

    private static boolean flag = false;

    private DestroySingle() {
        if (flag == false){
            flag = true;
        }else{
            throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
        }
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private static volatile DestroySingle LazyMan;

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

    public static void main(String[] args) throws Exception {
        Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
        //通过单例获取实例
        DestroySingle instance1 = declaredConstructor.newInstance();
        //通过反射获取实例
        declaredConstructor.setAccessible(true);
        DestroySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
    }
}
 /*================= 输出结果================
    main - > 创建了单例对象
	Exception in thread "main" java.lang.reflect.InvocationTargetException
		at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
		at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
		at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
		at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
		at org.javaboy.vhr.controller.system.DestroySingle.main(DestroySingle.java:37)
	Caused by: java.lang.RuntimeException: 单例对象,拒绝反射破坏操作!!
		at org.javaboy.vhr.controller.system.DestroySingle.(DestroySingle.java:13)
		... 5 more
================= 输出结果================*/

运行结果第一个实例成功创建,创建第二个实例则创建失败;

4.3.3 反射破坏升级Plus

当设置的标志变量被解密获取到:

public class DestroySingle {

    private static boolean flag = false;

    private DestroySingle() {
        if (flag == false){
            flag = true;
        }else{
            throw new RuntimeException("单例对象,拒绝反射破坏操作!!");
        }
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    private static volatile DestroySingle LazyMan;

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

    public static void main(String[] args) throws Exception {
        Constructor<DestroySingle> declaredConstructor = DestroySingle.class.getDeclaredConstructor(null);
        Field flag = DestroySingle.class.getDeclaredField("flag");
        declaredConstructor.setAccessible(true);
        //通过单例获取实例
        DestroySingle instance1 = declaredConstructor.newInstance();
        flag.set(instance1,false);
        //通过反射获取实例
        DestroySingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
    }
}
 /*================= 输出结果================
    main - > 创建了单例对象
    main - > 创建了单例对象
    org.javaboy.vhr.controller.system.DestroySingle@7ba4f24f
    org.javaboy.vhr.controller.system.DestroySingle@3b9a45b3
    instance1 == instance2:false
================= 输出结果================*/

经过升级Plus的破坏操作后,单例模式再次创建了两个实例对象,破坏成功;

4.3.4 破坏(静态内部类)

public class HolderSingle {
    private HolderSingle() {
        System.out.println(Thread.currentThread().getName() + " - > 创建了单例对象");
    }

    public static HolderSingle getInstance(){
        return InnerClass.LazyMan;
    }

    private static class InnerClass{
        private static final HolderSingle LazyMan = new HolderSingle();
    }

    public static void main(String[] args) throws Exception {
        //通过静态单例获取实例
        HolderSingle instance1 = HolderSingle.getInstance();
        //通过反射获取实例
        Constructor<HolderSingle> declaredConstructor = HolderSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        HolderSingle instance2 = declaredConstructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
    }
}
 /*================= 输出结果================
    main - > 创建了单例对象
    main - > 创建了单例对象
    org.javaboy.vhr.controller.system.HolderSingle@7cca494b
    org.javaboy.vhr.controller.system.HolderSingle@7ba4f24f
    instance1 == instance2:false
================= 输出结果================*/

以上的所谓单例模式都被一一破坏,接下来看看真正的单例模式。

4.4 枚举类的单例模式

  • 先来看一下Constructor.newInstance()方法的源码:
    Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第4张图片
    em…Cannot reflectively create enum objects? 不能通过反射创建枚举对象?很好,让我们来试试

4.4.1 反射破坏失败了?

public enum EnumSingle {
    INSTANCE;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}
class EnumTest{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.getInstance();
        EnumSingle instance2 = EnumSingle.getInstance();
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
        System.out.println("=================================分割线=====================================");
        //用反射来试一下
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance3 = declaredConstructor.newInstance();
        EnumSingle instance4 = declaredConstructor.newInstance();
        System.out.println("instance3 == instance4:"+(instance3 == instance4));
    }
}
 /*================= 输出结果================
    instance1 == instance2:true
    =================================分割线=====================================
    Exception in thread "main" java.lang.NoSuchMethodException: com.inspur.vista.ylt.conver.api.single.EnumSingle.()
        at java.lang.Class.getConstructor0(Class.java:3082)
        at java.lang.Class.getDeclaredConstructor(Class.java:2178)
        at com.inspur.vista.ylt.conver.api.single.EnumTest.main(EnumSingle.java:28)
================= 输出结果================*/

没有无参构造方法?进入到EnumSingle.java的所在文件目录,javap反编译看看结果:
Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第5张图片
编译结果可以看到,EnumSingle类中是存在着一个无参构造函数的,但是为什么在进行反射调用时却没找到呢?

那就用jad.exe1生成EnumSingle源码来看看是什么原因

在这里插入图片描述
Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第6张图片
EnumSingle.java源码图片:
Java 1.单例模式详解(饿汉式、懒汉式、DCL双重检测锁模式、静态内部类、枚举)_第7张图片

既然知道了构造参数的参数类型,那就按照要求在试试:

public enum EnumSingle {
    INSTANCE;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}
class EnumTest{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.getInstance();
        EnumSingle instance2 = EnumSingle.getInstance();
        System.out.println("instance1 == instance2:"+(instance1 == instance2));
        System.out.println("=================================分割线=====================================");
        //用反射来试一下
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance3 = declaredConstructor.newInstance();
        EnumSingle instance4 = declaredConstructor.newInstance();
        System.out.println("instance3 == instance4:"+(instance3 == instance4));
    }
}
 /*================= 输出结果================
    instance1 == instance2:true
    =================================分割线=====================================
    Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
        at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
        at cn.zx.single.EnumTest.main(EnumSingle.java:28)
================= 输出结果================*/

运行结果抛出了java.lang.IllegalArgumentException: Cannot reflectively create enum objects,这正是Constructor.newInstance()源码中的展现出来的代码,证明了不能通过反射创建枚举对象(破案,破案!),防守成功!

如果觉得文章不错,就给点个赞吧!


  1. 一个反编译工具,通过反编译将.class转成.java文件源码 ↩︎

你可能感兴趣的:(设计模式,java,后端)