设计模式之单例模式(懒汉, 饿汉)_懒汉模式用例图怎么画

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

二. 单例模式的实现

1. 饿汉模式

单例模式中, 一个类只能有一个实例对象, 我们可以使用static成员变量来保存这个唯一实例对象的引用, 同时, 为了防止类被创建多个实例对象, 需要将类构造方法封装为private修饰的私有方法, 在使用该类时必须要得到该类的这个实例对象, 所以我们还需要创建一个对外公开的获取该唯一实例的方法getInstance.

class Singleton {
    //使用static变量存储实例对象,类加载时创建对象.
    private static Singleton intance = new Singleton();
    //对外提供公开的方法获取该类的唯一实例.
    public static Singleton getInstance() {
        return intance;
    }
    //封装构造方法,防止该类被实例出新的对象
    private Singleton() {}
}

单线程情况下不存在线程安全的问题, 我们需要考虑多线程情况下上面的代码是否存在线程安全问题, 重点要看读写情况下的线程安全问题, 上面的代码只有getInstance涉及到读操作, 所以上面的代码是线程安全的.

2. 懒汉模式

懒汉式与饿汉明显的区别就是对象创建的时机不同, 懒汉式要等到第一次使用实例对象的时候才会创建对象, 所以我们将上面的代码稍做修改写出下面的代码.

class SingletonLazy {
    //在使用的时候创建对象,这里不初始化实例对象
    private volatile static SingletonLazy instance = null;
    //对外提供公开的方法获取该类的唯一实例,如果没有就创建
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
	//封装构造方法,防止该类被实例出新的对象
    private SingletonLazy() {}
}

那么再来考虑上面的代码在多线程情况下是否有线程安全问题存在, 代码中的getInstance方法中存在两次读操作(一次比较一次返回)和一次写操作(修改intsance变量的值),第一次创建对象时(即instance=null)可能会存在多个线程进入判断语句, 此时就可能会创建出多个实例对象, 看下图理解:

img

所以上面写出来的代码可能会触发多次new操作, 显然就不是单例了, 也就是说上面写出来的懒汉式的代码是线程不安全的.

导致上面的线程安全问题的主要原因是读, 比较和写这三个操作不是原子的, 这就导致了t2读到的值可能是t1还没来得及写的(脏读), 所以我们只需要将代码中的读写操作加锁即可这里的问题, 得到下面的代码:

class SingletonLazy {
    private volatile static SingletonLazy instance = null;

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

    private SingletonLazy() {}
}

但代码写到这里还是有问题的, 上述的线程安全问题只出现在instance没有初始化的时候, 如果instance已经有了实例对象, 后续调用getlnstanceinstance的值一定是非空的, 因此就会直接触发return, 相当于一个是比较操作, 一个是返回操作, 这就都是读操作了, 此时就不涉及线程安全问题了.

而上面的代码, 不论instance是否已经初始化, 每次调用getlnstance, 都需要进行加锁操作, 要知道加锁操作是有开销的, 在instance已经有了实例对象以后就完全没有必要去加锁了, 所以代码还需要修改, 在加锁前需要先判断一下instance是否已经初始化, 如果还没初始化就进行加锁, 如果实例对象创建了, 就不加锁了.

调整后的获取实例对象的代码如下:

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

到这里其实还没完, 还要考虑编译器优化造成的内存可见性和指令重排序的问题, 上面的代码中是有判断操作的(instance==null), 假设有很多线程, 都去进行getlnstance, 就会进行频繁的判断, 这时候就会有被优化的风险, 可能只有第一次读才是真正读了内存, 后续都是读寄存器/cache中的值, 这时候instance值变化时, 线程就感知不到了, 所以这里的内存可见性问题需要解决.

还有要注意这里的instance = new Singleton();操作, 拆分成三个步骤就是:

  1. 申请内存空间.
  2. 调用构造方法, 把这个内存空间初始化成一个合理的对象.
  3. 把内存空间的地址赋值给instance引用.

正常情况下,是按照123这个顺序来执行的, 但你防不住编译器的优化操作, 也就是指令重排序问题, 编译器为了提高程序效率, 可能就调整了代码执行顺序, 123这个顺序就可能变成132, 如果是单线程,123和132没有本质区别, 但是多线程环境下, 就会有问题了;

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

外链图片转存中…(img-iXea6xVS-1713434904957)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值