手撕单例模式(详解)

一:设计模式概述

1:设计模式的概念

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。(也就是给我们一种设计代码的模板 避免我们的代码错误太多)

2:学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下优点。

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
    (写的代码更加规范。可读性,可靠性更高,可重用性高);

3:软件设计模式的基本要素

这个也就是我们的设计模式的名称,应用场景(遇到的什么问题),解决方案,使用该设计模式后的效果

  • 每一个模式都有自己的名字,通常用一两个词来描述,可以根据模式的问题、特点、解决方案、功能和效果来命名。模式名称(PatternName)有助于我们理解和记忆该模式,也方便我们来讨论自己的设计。
  • 问题(Problem)描述了该模式的应用环境,即何时使用该模式。它解释了设计问题和问题存在的前因后果,以及必须满足的一系列先决条件。
  • 模式问题的解决方案(Solution)包括设计的组成成分、它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象的 组合)来解决这个问题
  • 描述了模式的应用效果以及使用该模式应该权衡的问题,即模式的优缺点。主要是对时间和空间的衡量,以及该模式对系统的灵活性、扩充性、可移植性的影响,也考虑其实现问题。显式地列出这些效果(Consequence)对理解和评价这些模式有很大的帮助。

4:设计模式的类型

在这里插入图片描述

二:单例模式

1:单例模式是个什么鬼

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

  • 1、单例类只能有一个实例。

  • 2、单例类必须自己创建自己的唯一实例。

  • 3、单例类必须给所有其他对象提供这一实例。
    关键词语: 创建型模式 自己创建实例,不能被实例化 单一对象,只能在堆当中有一个实例

2:单例模式的使用

  • **意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。
  • **主要解决:**一个全局使用的类频繁地创建与销毁。
  • **何时使用:**当您想控制实例数目,节省系统资源的时候。
  • **如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
  • **关键代码:**构造函数是私有的。
  • 应用实例:
    • 1、一个班级只有一个班主任。
    • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
    • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
  • 优点:
    • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
      (这里补充类加载,我们new 一个实例的时候 该类会被 加载;或则调用某个类的静态方法时候也会被加载,或者是该子类被加载的时候,父类也会 被加载)

    • 2、避免对资源的多重占用(比如写文件操作)。(无论谁去 调用 我们这个实例 该实例 在内存中只有一个)

  • **缺点:**没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
  • 使用场景
    • 1、要求生产唯一序列号。
    • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
    • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
      **注意事项:**getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

3:单例实现过程

(1):创建单例类的步骤

  • 创建一个该单例类的对象(需要用static 修饰为静态属性 因为在下方的静态方法中只能用静态属性,而且下方的方法也必须是静态方法,因为静态方法被调用时,该类就会被类加载到jvm的内存中)
  • 让构造函数为私有的 这样该类 就不会被实例化
  • 对外提供唯一的可用对象

(2):代码

package com.wyj.singleton;
class SingleObject {
    //创建一个SingleObject对象
    // 用static 关键字修饰 表示该变量为静态变量  该类所有对象所共享的一个变量
    //下方static方法中 不能调用非Static属性
    private static SingleObject getInstance = new SingleObject();
    private String name;
    //让其构造函数为private,那么他不会被实例化
    private SingleObject (){
    }
    //获取唯一可用的对象
    // 这里用static关键字修饰的话, 当该静态方法被调用时该类就会被类加载
    public static SingleObject getSingleObject() {
        return getInstance;
    }
    void say() {
        System.out.println("hello");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class singleDemo {
    public static void main(String[] args) {
        // 我们并没有去new一个实例,而是调用了该实例的 自己建造实例的方法
        SingleObject object = SingleObject.getSingleObject();
        SingleObject object2 = SingleObject.getSingleObject();
        object.setName("wyj");
        object2.setName("www");
        System.out.println(object.getName());

    }
}

(3):验证每个引用变量共享一个实例

在这里插入图片描述

上方中我们创建了两个引用变量,我们同时操作同一个属性

4:几种单例实现的方式

(1):前言

  • lazy初始化:表示的是否延迟加载,它的核心思想是把对象的实例化延迟到真正调用该对象的时候,这样做的好处是可以减轻大量对象在实例化时对资源的小号,而不是在程序初始化的时候就预先将对象实例化。
    (也就是在堆中开辟空间是否延迟,因为一旦调用static 修饰的方法 那么该类就会被加载,无论懒汉式 还是饿汉式 单例类 他们均需被加载,那么这里的延迟加载 指的是在懒汉式中 我们只有调用到 getInstance() 才会在 堆空间 内为 该实例开辟 开辟空间;而饿汉式当中 只要当我们类加载初始化 就会在堆中开辟实例空间。)

(2):懒汉式,线程不安全

  • 懒汉式单例模式 只是声明对象,等到调用getInstanc()的时候才进行new 对象 因为他懒
  • **是否 Lazy 初始化:**是
  • **是否多线程安全:**否
  • **实现难度:**易
  • **描述:**这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。(当出现多个线程调用该方法时,会出现多个实例)
  • 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
class lazyUnsafe {
    private static lazyUnsafe lazyUnsafe;

    private lazyUnsafe(){//new 实例的时候会调用这个构造方法
      System.out.println(Thread.currentThread().getName());
    }

    public static lazyUnsafe getInstance() {
        if (lazyUnsafe == null) {
            //这里的线程不安全 就是发生在这里 如果 A线程执行到这里 正好被阻塞了
            //然后CPU的时间片切换到B线程 然后B线程 new 了一个实例 那么的话
            //当CPU再次切换到A线程的时候,那么的话就又会new了一个实例 这样就破坏了单实例
            //
            lazyUnsafe = new lazyUnsafe();
        }
        return lazyUnsafe;
    }
    
    public static void main(String[] args) {
      for (int i = 0; i < 10; i++) {
          new Thread( ()->{
              lazyUnsafe.getInstance();//这里运行出几个线程  那就创造出几个实例 破坏了单例原则
          }                           // 在new lazyUnsafe()的时候  会调用空参构造方法,那么就会输出线程名
          ).start();
      }
}  

}

(3):懒汉式线程安全

  • **是否 Lazy 初始化:**是
  • **是否多线程安全:**是
  • **实现难度:**易
  • **描述:**这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
    优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

  • 解释线程安全:
    虽然我们开启了10个线程,但是只有一个线程可以new LazyUnsafe() 调用构造方法输出线程名

      因为我们加锁了, 在一次运行当中,所有线程共享jvm中的堆区,也就是共享我们的
    

同一个在堆中new出的实例,那么后面的线程调用方法getInstance()的话,因为 lazyUnsafe != null 了 所以不会再 new 实例了

class lazysafe {
    private static lazySafe lazysafe;

    private lazySafe(){
        System.out.println(Thread.currentThread().getName());
    }

    public static synchronized lazySafe getInstance() {
        if (lazysafe == null) {
            lazysafe = new lazySafe();
        }
        return lazyUnsafe;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread( ()-> {
                lazySafe.getInstance();//虽然我们开启了10个线程
                                        //但是只有一个线程可以new LazyUnsafe() 调用构造方法输出线程名
            }).start();                  //同一个在堆中new出的实例,那么后面的线程调用方法getInstance()的话  
}                                   //因为 lazyUnsafe != null 了 所以不会再 new 实例了
        }

    }

(4):双检锁/双重效验锁(DCL)

  • **DK 版本:**JDK1.5 起
  • **是否 Lazy 初始化:**是
  • **是否多线程安全:**是
  • **实现难度:**较复杂
  • **描述:**这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
  • 创建一个SingleLazyDCL的对象时加入了 volatitle关键字
    • 解决了什么问题?
      我们在进行 new SingleLazyDCL() 时 这不是一个原子操作,有三个过程

       1. 为该实例分配内存空间
       2. 执行构造方法 初始化对象
       3. 栈中引用变量指向 堆中的实例
      

如果正常执行 1 2 3 那也没啥问题

但是这里会出现指令重排 也就是 会出现 1 3 2

        * 那么当A线程指令执行到3时还未执行2指令时
        * 这时候CPU切换到B线程来执行,那么在第一个 if()判断时 因为 此时对象引用不为空了,那么就会直接返回这个对象的引用,但是这个对象还未执行构造方法初始化,那就返回了个残缺不全的对象  会报错。
* 为什么加入这个关键字呢

那么 我们加入了这个关键字就可以防止指令重排。

  • 为什么是两层 if 判断
    • 第一个if语句,用来确认调用getInstance()时instance是否为空,如果不为空即已经创建,则直接返回,如果为空,那么就需要创建实例,于是进入synchronized同步块。

    • synchronized加类锁,确保同时只有一个线程能进入,进入以后进行第二次判断,是因为,
      对于首个拿锁者,它的时段instance肯定为null,那么进入new Singleton()对象创建,

    • 在首个拿锁者的创建对象期间,可能有其他线程同步调用getInstance(),那么它们也会通过if进入到同步块试图拿锁然后阻塞(因为这时首拿锁的线程还在创建对象期间)。

    • 这样的话,当首个拿锁者完成了对象创建,之后的线程都不会通过第一个if了,而这期间阻塞的线程开始唤醒,它们则需要靠第二个if语句来避免再次创建对象。

    • 以上就是双检索的实现思路,synchronized与第二个if即是用来保证线程安全与不产生第二个实例

//双层锁的机制
public class SingleLazyDCL {

    private volatile static SingleLazyDCL singleLazyDCL;

    private SingleLazyDCL (){
        System.out.println(Thread.currentThread().getName());
    }

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

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

    }

}

(5):静态内部类 (懒汉单例)

  • 这里的体现单例模式是在于我们用的final关键字,当final修饰引用类型的变量的话,则在其初始化后就不能然后其指向另一个对象,那么但我们多个线程去调用getInstance()方法时
    由于已经有一个线程是第一个SingleInner singleInner = new SingleInner(),那么的话

那么接下来再有线程去 SingleHolder.singleInner的话是不能进行的,因为final修饰的引用变量,再其初始化后,就不能再去指向另外一个对象(实例)

  • **是否 Lazy 初始化:**是
  • **是否多线程安全:**是
  • **实现难度:**一般
  
public class SingleInner {

    private SingleInner(){
        System.out.println(Thread.currentThread().getName());
    }

    //final修饰的对于一个final变量,
    // 如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;
    // 如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
    private static class SingleHolder{
        private static final SingleInner singleInner = new SingleInner();
    }

    public static SingleInner getInstance() {
        return SingleHolder.singleInner;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingleInner.getInstance();
            }).start();
        }



    }

(6):禁止反射破坏的(懒汉单例)

a:第一次破坏和防御

1:先展示反射如何破坏

package com.wyj.singleton;

import java.lang.reflect.Constructor;

public class ReflectBreakSingle {

    private ReflectBreakSingle() {
        System.out.println(Thread.currentThread().getName());
    }

    private static volatile ReflectBreakSingle reflectBreakSingle;

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

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

        //正常情况下 调用两次getInstance()的话,我们产生两个引用变量,但是只有一个实例
        ReflectBreakSingle instance1 = getInstance();
        ReflectBreakSingle instance2 = getInstance();

        System.out.println("正常情况:" + (instance1 == instance2));//返回true 因为我们的引用变量地址相同



        //利用反射破坏

        //通过反射获取该类的Class对象
        Class<?> aClass = Class.forName("com.wyj.singleton.ReflectBreakSingle");
        //通过Class对象获取该类的 空参构造器
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        //爆破  破坏该类的空参的私有属性
        declaredConstructor.setAccessible(true);

        //通过反射获取该类实例 也就是相当于 new ReflectBreakSingle();
        ReflectBreakSingle instance3 = (ReflectBreakSingle) aClass.newInstance();

        //调用getInstance再来一次获取获取该类的实例
        ReflectBreakSingle instance4 = getInstance();

        //如果单例没有被破坏的话 那么这两个实例返回的是true,因为堆中只有一个实例,但是单例被破坏了的话就会产生两个实例
        //那么引用变量对应的地址的值是不相同的
        System.out.println("利用反射破坏" + (instance3 == instance4));



    }
}

2:懒汉单例进行防御
其实也就是在我们的 空参构造器中加入一个判断,如果已经建立过一次了就不允许再创建了

package com.wyj.singleton;

import java.lang.reflect.Constructor;

public class ReflectBreakSingle {

    private ReflectBreakSingle() {
       if(reflectBreakSingle != null) {
           throw new RuntimeException("不允许反射破坏");
       }
    }

    private static volatile ReflectBreakSingle reflectBreakSingle;

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

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

        //正常情况下 调用两次getInstance()的话,我们产生两个引用变量,但是只有一个实例
        ReflectBreakSingle instance1 = getInstance();
        ReflectBreakSingle instance2 = getInstance();

        System.out.println("正常情况:" + (instance1 == instance2));//返回true 因为我们的引用变量地址相同



        //利用反射破坏

        //通过反射获取该类的Class对象
        Class<?> aClass = Class.forName("com.wyj.singleton.ReflectBreakSingle");
        //通过Class对象获取该类的 空参构造器
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        //爆破  破坏该类的空参的私有属性
        declaredConstructor.setAccessible(true);

        //通过反射获取该类实例 也就是相当于 new ReflectBreakSingle();
        ReflectBreakSingle instance3 = (ReflectBreakSingle) aClass.newInstance();

        //调用getInstance再来一次获取获取该类的实例
        ReflectBreakSingle instance4 = getInstance();

        //如果单例没有被破坏的话 那么这两个实例返回的是true,因为堆中只有一个实例,但是单例被破坏了的话就会产生两个实例
        //那么引用变量对应的地址的值是不相同的
        System.out.println("利用反射破坏" + (instance3 == instance4));

    }
}
b:第二次破坏和防御
  • 破坏的话 我们是直接用反射创建两个实例 那么我们就没有调用 getInstance()方法了,这样的话 我们的单例中的静态变量 就没有被赋值了,所以那个构造函数中的判空 就不起作用了。
package com.wyj.singleton;

import java.lang.reflect.Constructor;

public class ReflectBreakSingle2 {
    private static volatile ReflectBreakSingle2 reflectBreakSingle;

    static {
        System.out.println("代码块:"+reflectBreakSingle);
    }



    private ReflectBreakSingle2() {
//        System.out.println("构造方法:"+reflectBreakSingle);
       if(reflectBreakSingle != null) {
           throw new RuntimeException("不允许反射破坏");
       }
    }

    public static ReflectBreakSingle2 getInstance() {
        if (reflectBreakSingle == null) {
            synchronized (ReflectBreakSingle2.class) {
                if (reflectBreakSingle == null) {
                    reflectBreakSingle = new ReflectBreakSingle2();
                    System.out.println("hello");
                }
            }
        }
        return reflectBreakSingle;
    }

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

        //正常情况下 调用两次getInstance()的话,我们产生两个引用变量,但是只有一个实例
//        ReflectBreakSingle instance1 = getInstance();
//        ReflectBreakSingle instance2 = getInstance();
//
//        System.out.println("正常情况:" + (instance1 == instance2));//返回true 因为我们的引用变量地址相同



        //利用反射破坏

        //通过反射获取该类的Class对象
        Class<?> aClass = Class.forName("com.wyj.singleton.ReflectBreakSingle2");
        //通过Class对象获取该类的 空参构造器
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        //爆破  破坏该类的空参的私有属性
        declaredConstructor.setAccessible(true);

        //通过反射获取该类实例 也就是相当于 new ReflectBreakSingle();
         ReflectBreakSingle2 instance3 = (ReflectBreakSingle2) aClass.newInstance();

        /**
         * 这样写的话就可以破坏单例模式,上方通过反射创建了一次实例,
         * 下方这里再通过getInstance()方法再创建一次实例,就破坏了单例模式,这里不会抛出异常
         * 因为第一创建实例的时候 我们并没有给
         *  private static volatile ReflectBreakSingle2 reflectBreakSingle;
         *  ReflectBreakSingle2 进行赋值,那么的话 我们在构造函数中的判空判断就失效了
         */
        getInstance();



    }
}
  • 那么单例是如何防御的呢
    首先我们选择一个静态的标志位变量, 因为是静态变量 所以只有我们在第一次进行类加载的时候进行加载,那么我们通过在构造方法中给其修改变量的值,那么的话下一次再创建实例的时候,我们构造方法中的设置的判断条件就会起作用了
package com.wyj.singleton;

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

public class ReflectBreakSingle3 {
    private static volatile ReflectBreakSingle3 reflectBreakSingle;
    private static boolean flag = false;

    private ReflectBreakSingle3() {
        if ( flag == false) {
            flag = true;
        } else {
             throw new RuntimeException("禁止破坏单例");
        }
    }



    public static ReflectBreakSingle3 getInstance() {
        if (reflectBreakSingle == null) {
            synchronized (ReflectBreakSingle3.class) {
                if (reflectBreakSingle == null) {
                    reflectBreakSingle = new ReflectBreakSingle3();
                    System.out.println("hello");
                }
            }
        }
        return reflectBreakSingle;
    }

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

        //利用反射破坏

        //通过反射获取该类的Class对象
        Class<?> aClass = Class.forName("com.wyj.singleton.ReflectBreakSingle3");
        //通过Class对象获取该类的 空参构造器
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        //爆破  破坏该类的空参的私有属性
        declaredConstructor.setAccessible(true);

        //通过反射获取该类实例 也就是相当于 new ReflectBreakSingle();
        ReflectBreakSingle3 instance3 = (ReflectBreakSingle3) aClass.newInstance();

        /**
         * 这样我们加入一个静态的标志位,那么在第二次new实例的时候由于 static 修饰的静态变量只会在第一次
         * new 实例的时候加载一次  那么我们在构造函数给其进行赋值的话  那么在第二次的new 实例的时候
         * 这个静态变量的值是不会变的
         */
        getInstance();

    }
}
c:反射还可以再进行破坏

我们通过反射获取到我们的这个静态变量,然后的话,我们修改这个静态变量的值,这样就可以破坏,构造方法中的判断条件。

package com.wyj.singleton;

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

public class ReflectBreakSingle4 {
    private static volatile ReflectBreakSingle4 reflectBreakSingle;
    private static boolean flag = false;

    private ReflectBreakSingle4() {
        if ( flag == false) {
            flag = true;
        } else {
             throw new RuntimeException("禁止破坏单例");
        }
    }



    public static ReflectBreakSingle4 getInstance() {
        if (reflectBreakSingle == null) {
            synchronized (ReflectBreakSingle4.class) {
                if (reflectBreakSingle == null) {
                    reflectBreakSingle = new ReflectBreakSingle4();
                    System.out.println("hello");
                }
            }
        }
        return reflectBreakSingle;
    }

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

        //利用反射破坏

        //通过反射获取该类的Class对象
        Class<?> aClass = Class.forName("com.wyj.singleton.ReflectBreakSingle4");
        //通过Class对象获取该类的 空参构造器
        Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(null);
        //爆破  破坏该类的空参的私有属性
        declaredConstructor.setAccessible(true);

        //通过反射获取该类实例 也就是相当于 new ReflectBreakSingle();
        ReflectBreakSingle4 instance3 = (ReflectBreakSingle4) aClass.newInstance();

        //我们通过反射获取到该字段在进行修改为false
        Field flag = ReflectBreakSingle4.class.getDeclaredField("flag");
        flag.set(instance3,false);



        /**
         * 这样我们加入一个静态的标志位,那么在第二次new实例的时候由于 static 修饰的静态变量只会在第一次
         * new 实例的时候加载一次  那么我们在构造函数给其进行赋值的话  那么在第二次的new 实例的时候
         * 这个静态变量的值是不会变的
         */
        getInstance();

    }
}

那么这时候我们就应该知道的是 我们得找一个不允许反射破坏的单例 那就是枚举

(7)饿汉式

  • **是否 Lazy 初始化:**否(延迟初始化)
  • **是否多线程安全:**是
  • **实现难度:**易
  • **描述:**这种方式比较常用,但容易产生垃圾对象。
  • 优点:没有加锁,执行效率会提高。
  • 缺点:类加载时就初始化,浪费内存。
    它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
package com.wyj.singleton;

public class SingleHungry {
    private static SingleHungry singleHungry = new SingleHungry();

    private SingleHungry(){
        System.out.println(Thread.currentThread().getName());
    }

    public static SingleHungry getInstance() {
        return singleHungry;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread( ()->{
                SingleHungry.getInstance();//这里不会输出构造器当中的方法了
            }).start();                   //因为调用该类的静态方法的时候,该类已经 n
        }                                //new 完实例了  那么我们就可以知道的是
    }                                   //那就没法再去 输出我们线程的名字    



}

(8):枚举单例

  • 要注意的是 枚举当中空参构造器 是假的 我们通过反编译可以得到的是 里面的构造器是
    带有两个参数的(String 和 int )
package com.wyj.singleton;

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

public enum SingleEnum {
    Instance;

    public SingleEnum getInstance() {
        return Instance;
    }



    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//        SingleEnum instance = SingleEnum.Instance;
//        SingleEnum instance2 = SingleEnum.Instance;
//
//        System.out.println(instance == instance2);



        Class<?> aClass = Class.forName("com.wyj.singleton.SingleEnum");

        //这里通过反编译的话:jad -s java SingleEnum.class
        // 我们得到了原来枚举其实不是空参构造器
        //真实的底层其实是:
        //  private SingleEnum(String s, int i)
        //    {
        //        super(s, i);
        //    }
        Constructor<?> constructor = aClass.getDeclaredConstructor(String.class,int.class);
        Object instance = constructor.newInstance();
        SingleEnum instance1 = SingleEnum.Instance;



    }

}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天向上的菜鸡杰!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值