前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >23种设计模式之单例模式

23种设计模式之单例模式

作者头像
暴躁的程序猿
发布2022-03-23 17:08:52
1510
发布2022-03-23 17:08:52
举报
文章被收录于专栏:阿飞的学习记录

单例模式

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

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

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

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

代码语言:javascript
复制
/**
 * 饿汉式单例
 * @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;
      }
}

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

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

懒汉模式 用的时候才加载

代码语言:javascript
复制
/**
 * 懒汉式单例
 * @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;
    }
    //单线程下但是是没问题的
}

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

代码语言:javascript
复制
public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                Lazy.getInstance();
            }).start();
        }
    }

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

在这里插入图片描述
在这里插入图片描述

所以我们要加锁 保证单例 DCL懒汉式 双重检查锁

代码语言:javascript
复制
/**
 * 懒汉式单例
 * @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();
        }
    }
}

测试

在这里插入图片描述
在这里插入图片描述

虽然这样就可以保证是单例的了 但是还有一个问题

代码语言:javascript
复制
lazyMan = new LazyMan();

不是原子性操作

new 的时候会有三个步骤 :

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

2.初始化对象

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

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

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

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

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

加入volatile

代码语言:javascript
复制
  private volatile static LazyMan lazyMan;
代码语言:javascript
复制
/**
 * 懒汉式单例
 * @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();
        }
    }
}

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

代码语言:javascript
复制
/**
 * 静态内部类
 * @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中的反射可以破解 获取到对象

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

代码语言:javascript
复制
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);
    }

}

测试结果

在这里插入图片描述
在这里插入图片描述

单例被反射破坏了

解决方法

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

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

代码语言:javascript
复制
 private Lazy(){
          synchronized(Lazy.class){
              if (LAZY!=null){
                  throw new RuntimeException("不要用反射破坏单例");
              }
          }
    }

测试

在这里插入图片描述
在这里插入图片描述

但是此时又有了新问题

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

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

代码语言:javascript
复制
 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);
    }

结果是反射又被破坏了

在这里插入图片描述
在这里插入图片描述

此时我们需要加入一个信号位 来判断

代码语言:javascript
复制
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);
    }

}

效果

在这里插入图片描述
在这里插入图片描述

成功解决的 反射问题

思考

这种方式还有什么弊端?

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/06/19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档