23种设计模式之单例模式

Posted 暴躁的程序猿啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了23种设计模式之单例模式相关的知识,希望对你有一定的参考价值。

单例模式

这里小编学习单例时参考了狂神的设计模式视频 链接在这里 设计模式

这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

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

单例模式实现之一 饿汉模式

/**
 * 饿汉式单例
 * @create: 2021/6/18
 * @author: Tony Stark
 */
public class Hungry {
    /**
     * 私有构造器可以保证只有一个对象
     */
      private Hungry(){

      }
      private final static Hungry HUNGRY=new Hungry();

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

饿汉模式的弊端就是加载的时候就创建 浪费资源 所以才有了懒汉式

单例模式实现之二 懒汉模式

懒汉模式 用的时候才加载

/**
 * 懒汉式单例
 * @create: 2021/6/18
 * @author: Tony Stark
 */
public class Lazy {
    /**
     * 构造器私有
     */
    private Lazy(){
        System.out.println(Thread.currentThread().getName()+"启动!");
    }
    private static Lazy lazy;

    public static Lazy getInstance(){
        if (lazyMan==null){
            lazyMan = new LazyMan();
        }
        return lazy;
    }
    //单线程下但是是没问题的
}

但是懒汉模式多线程下就失效了

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

多线程下会出现问题 单例模式失效

在这里插入图片描述
所以我们要加锁 保证单例 DCL懒汉式 双重检查锁

/**
 * 懒汉式单例
 * @create: 2021/6/18
 * @author: Tony Stark
 */
public class Lazy {
    /**
     * 构造器私有
     */
    private Lazy(){
        System.out.println(Thread.currentThread().getName()+"启动!");
    }
    private static Lazy lazy;

    /**
     * 双重检测锁模式的懒汉单例模式  简称DCL懒汉式
     * @return
     */
    public static Lazy getInstance(){
        //解决多线程问题   加锁  可能加锁之前被两个线程拿到   所以我们要进行两次检测
        if (lazy==null){
            synchronized(Lazy.class){
                if (lazy==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    //单线程下但是是没问题的

    /**
     *多线程并发
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

测试
在这里插入图片描述
虽然这样就可以保证是单例的了 但是还有一个问题

lazyMan = new LazyMan();

不是原子性操作

new 的时候会有三个步骤 :

1.分配内存空间执行构造方法

2.初始化对象

3.把这个对象指向这个空间

虽然看起来 new 是一步操作 实际上它会进行以上三步操作 就会发生一个指令重排

原来可能是 123 的顺序执行 重排后可能会变为 231

即是当一个线程执行1之后执行了3步骤 先把对象指向了开辟的空间  但还未初始化对象  
这时另一条线程 进行判断时 对象不为空   直接返回对象   但对象还未初始化   所以会出错

为了防止指令重排 我们要保证lazyMan的操作

加入volatile

  private volatile static LazyMan lazyMan;
/**
 * 懒汉式单例
 * @create: 2021/6/18
 * @author: Tony Stark
 */
public class Lazy {
    /**
     * 构造器私有
     */
    private Lazy(){
        System.out.println(Thread.currentThread().getName()+"启动!");
    }
  private volatile static Lazy lazy;

    /**
     * 双重检测锁模式的懒汉单例模式  简称DCL懒汉式
     * @return
     */
    public static Lazy getInstance(){
        //解决多线程问题   加锁  可能加锁之前被两个线程拿到   所以我们要进行两次检测
        if (lazy==null){
            synchronized(Lazy.class){
                if (lazy==null){
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    //单线程下但是是没问题的

    /**
     *多线程并发
     */
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }
}

单例模式实现之三 内部类实现模式

/**
 * 静态内部类
 * @create: 2021/6/18
 * @author: Tony Stark
 */
public class External {

    private External (){

    }

    public static External getInstance(){
        return InnerClass.EXTERNAL;
    }

     public static class InnerClass{
            private static final External EXTERNAL=new External ();

     }
      public static void main(String[] args) {
        External instance = External .getInstance();
        External instance1 = External .getInstance();
        System.out.println(instance==instance1);
    }
}


在这里插入图片描述
安全问题

但以上方式都是不安全的

Java中的反射可以破解 获取到对象

我们在上述代码中加入反射获取对象

import java.lang.reflect.Constructor;

/**
 * @create: 2021/6/19
 * @author: Tony Stark
 */
public class Lazy {

    private Lazy(){

    }

    private volatile static Lazy LAZY;

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

    public static void main(String[] args) throws Exception{
        //获取实例
        Lazy lazy = Lazy.getInstance();
        //反射获取实例
        Class<Lazy> lazyClass = Lazy.class;
        Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
        //跳过安全检查
        constructor.setAccessible(true);
        //获取实例对象
        Lazy lazy1 = constructor.newInstance();
        //判断反射获取实例和正常获取的实例是否是同一个对象
        System.out.println(lazy==lazy1);
    }

}

测试结果
在这里插入图片描述
单例被反射破坏了

解决方法

反射通过我们的无参构造器获取对象 我们可以再无参构造器中加入判断

判断已经有了对象之后就不可以再创建对象了

 private Lazy(){
          synchronized(Lazy.class){
              if (LAZY!=null){
                  throw new RuntimeException("不要用反射破坏单例");
              }
          }
    }

测试
在这里插入图片描述
但是此时又有了新问题

因为刚刚我们第一个对象是用getInstance new 出来的 所以有了一个对象 就无法反射了

如果我们都用反射创建呢 ?

 public static void main(String[] args) throws Exception{
        //获取实例
       // Lazy lazy = Lazy.getInstance();
        //反射获取实例
        Class<Lazy> lazyClass = Lazy.class;
        Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
        //跳过安全检查
        constructor.setAccessible(true);
        //获取实例对象
        Lazy lazy1 = constructor.newInstance();
        Lazy lazy2 = constructor.newInstance();
        //判断反射获取实例和正常获取的实例是否是同一个对象
        System.out.println(lazy2==lazy1);
    }

结果是反射又被破坏了
在这里插入图片描述
此时我们需要加入一个信号位 来判断

public class Lazy {

    //加入一个信号位
    private static boolean feifei=false;

    private Lazy(){
          synchronized(Lazy.class){
              if(feifei==false){
                  //更改信号位
                  feifei=true;
              }else{
                  throw new RuntimeException("不要用反射破坏单例");
              }
          }
    }

    private volatile static Lazy LAZY;

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

    public static void main(String[] args) throws Exception{
        //获取实例
//        Lazy lazy = Lazy.getInstance();
        //反射获取实例
        Class<Lazy> lazyClass = Lazy.class;
        Constructor<Lazy> constructor = lazyClass.getDeclaredConstructor(null);
        //跳过安全检查
        constructor.setAccessible(true);
        //获取实例对象
        Lazy lazy1 = constructor.newInstance();
        Lazy lazy2 = constructor.newInstance();
        //判断反射获取实例和正常获取的实例是否是同一个对象
        System.out.println(lazy2==lazy1);
    }

}

效果
在这里插入图片描述
成功解决的 反射问题

思考

这种方式还有什么弊端?

以上是关于23种设计模式之单例模式的主要内容,如果未能解决你的问题,请参考以下文章

23种设计模式之单例模式

GoF 23 种设计模式之单例模式

23种设计模式之单例模式

最全23种设计模式之单例模式(Singleton)

23种设计模式之单例模式

23种设计模式之单例设计模式