设计模式--单例模式说明


前言

设计模式是在软件设计中常见的一种解决问题的方法论,它是经过反复验证和实践的一种通用的解决方案,用来解决在软件设计中常见的问题。

设计模式就是套路,就好像你学习六级的时候,针对阅读问题有阅读的套路,针对听力问题有听力的套路,你根据那些套路,可以更好的做题。在软件开发中也有很多常见的"问题场景",针对这些问题场景,大佬们总结了一些套路,按照套路来实现代码,会更顺利,减少出错的可能性。

今天我们来说一下单例模式。


一、单例模式

单例模式的意思就是能保证某个类在程序中只存在唯一一份实例,而不会创建多个实例,比如在JDBC的DataSource实例只有一个。单例模式具体的实现方式, 分成 “饿汉” 和 “懒汉” 两种

1.饿汉模式

1.代码示例

class Singleton {
    //创建实例
    private static Singleton instance = new Singleton();

    //如果需要使用这个实例,统一通过Singleton.getInstance()获取
    public static Singleton getInstance() {
        return instance;
    }

    //将构造方法设置为private,防止外界使用new方法进行实例化
    private Singleton() {
    }
}

public class test4 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        //Singleton s3 = new Singleton();
        System.out.println(s1 == s2);
    }
}

运行结果

在这里插入图片描述

2.解释说明

1.为什么叫饿汉模式: 在类加载阶段,就把实例创建出来,给人一种比较急切的感觉

补充:Java程序的执行可以分为以下几个阶段:
1.源代码阶段
2.编译阶段: 主要是将Java文件编译成.class文件,生成字节码
3.类加载阶段: Java进程能够找到并读取对应的Java文件,就会读取文件内容,并解析,构造成类对象…这一系列操作过程称为类加载阶段
4.运行阶段

2.保证实例唯一: 1.static保证实例唯一;2.构造方法是设为private,外面的代码中无法new

2.懒汉模式

1.解释说明

class SingletonLazy {
    //创建实例
    private static SingletonLazy instance = new SingletonLazy();

    //如果需要使用这个实例,统一通过SingletonLazy.getInstance()获取
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    //将构造方法设置为private,防止外界使用new方法进行实例化
    private SingletonLazy() {
    }
}

public class test5 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        //Singleton s3 = new Singleton();
        System.out.println(s1 == s2);
    }
}


运行结果

在这里插入图片描述

2.解释说明

在这个代码中,这个实例并非在类加载的时候创建,而是真正第一次使用的时候去创建,相比上一个在类加载阶段就把实例创建出来,就比较懒了,所以叫懒汉模式

二、单例模式中线程安全问题

在上面的饿汉模式和懒汉模式中,如果在多线程环境下调用getInstance(),是否线程安全?

在下面中我们可以看到饿汉模式只有读操作,线程是安全的,而懒汉模式既有读操作又有写操作,线程是不安全的

在这里插入图片描述

在懒汉模式代码下,下面有t1和t2两个线程分别读取instance的值是否为空,若为空则进行new操作,在下图中我们可以看出读和写这两个操作不是原子的,导致t2读到的值可能是t1还没有来得及修改的值,从而造成了脏读,导致分别进行了两次new操作,不符合单例模式只允许存在一个实例,而不会创建多个实例的情况,从而存在线程安全问题

在这里插入图片描述

如何解决上面的问题,是懒汉模式能成为线程安全,方法是加锁

在这里插入图片描述

这个锁要加在外面,从而保证读写是原子性的

class SingletonLazy {
    //创建实例
    private static SingletonLazy instance = new SingletonLazy();

    //如果需要使用这个实例,统一通过SingletonLazy.getInstance()获取
    public static SingletonLazy getInstance() {
        synchronized (SingletonLazy.class) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }

    //将构造方法设置为private,防止外界使用new方法进行实例化
    private SingletonLazy() {
    }
}

思考: 如果锁加在里面,那么这个加锁操作会有效吗?显然是不有效的,因为并没有将instance的读取操作进行加锁,此时的读和写操作仍为非原子的

在这里插入图片描述

但是上面的代码即使进行了加锁还是会有问题的,加锁操作会有时间开销的,如果每一次调用这个getInstance都需要加锁,那时间开销就会很大,这个代码是否真的每一次都需要加锁呢?

显然不是的,上面代码中的加锁只是针对new出对象以前要进行加锁,一旦对象已经new出来了,单例模式明确限制只能有一个实例,所以当new出对象后,此时的instance一直都是非空的,直接触发return,相当于这时候的代码只有一个比较非空和返回操作,均为读操作,此时不加锁也是没有关系的

优化

class SingletonLazy {
    //创建实例
    private static SingletonLazy instance = new SingletonLazy();

    //如果需要使用这个实例,统一通过SingletonLazy.getInstance()获取
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    //将构造方法设置为private,防止外界使用new方法进行实例化
    private SingletonLazy() {
    }
}

上面有两次if (instance == null)的判断,但是这两次判断的目的是完全不相同的,第一个if (instance == null)负责判定是否需要添加锁,第二个if (instance == null)负责的是是否需要创建对象

但是上面的代码进行优化后,还会有问题

1.内存可见性问题

假设有很多个线程,都去进行getInstance(),这个时候就可能存在内存可见性问题,这是因为在多线程环境下,一个线程对instance的修改,可能并不会被其他线程给立即感知,导致其他线程拿到的instance并不是最新的

2.指令重排序问题

在执行instance = new SingletonLazy();这行代码时,实际上会分为三步操作:1.分配内存、2.初始化对象、3.设置instance指向刚分配的内存地址。由于这三步操作不是原子性的,所以在多线程环境下,就可能出现指令重排序的问题。

可以形象的理解为你在食堂打饭的情形,分为三个步骤:1.阿姨拿出饭盘 2.阿姨给你打饭 3.阿姨把饭给你,在指令重排序中可能变为132,那么就变成:1.阿姨拿出饭盘 3.阿姨把饭给你 2.阿姨给你打饭,这时候你虽然拿到了饭盘,但是你的盘中还没有饭,从而出现问题。

解决方案:添加volatile,volatile既可以解决内存可见性问题,也可以解决指令重排序问题

class SingletonLazy {
    //创建实例
    private static volatile SingletonLazy instance = new SingletonLazy();

    //如果需要使用这个实例,统一通过SingletonLazy.getInstance()获取
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    //将构造方法设置为private,防止外界使用new方法进行实例化
    private SingletonLazy() {
    }
}


总结

单例模式是一种设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在多线程环境下,为了避免多个线程同时创建实例导致的问题,可以使用双重检查锁定模式来确保单例对象的原子性创建。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值