单例模式下的懒汉模式及饿汉模式

单例模式

单例模式对应的场景,有些时候,希望有的对象,在整个程序中只能有一个实例(对象)(也就是只能 new 一次)类似于JDBC DataSource这样的对象,就只需要一个即可.当然了,靠人来保证是非常不靠谱的,此时就需要靠编译器来进行一个更严格的检查.我们让代码中只能 new 一次对象,如果尝试 new 多个对象,就会直接报错(在Java中有很多种写法可以达成这样的效果,但是这里我们只介绍两种就够了)

饿汉模式(迫切)

程序启动,类加载之后,立即创建出实例

我们怎么才能让这个对象不能再次被 new 出来?

答:将它的构造方法用private所修饰.

注意:用static所修饰的变量,方法,叫做类方法,类变量,此时只能使用"类名."的方式才能调用出来.这种方法就可以保证我们在使用这个类中的一些方法的时候,就只能有一个对象了

那么,把构造方法设置成private就真的在外面调用不了吗?

其实不是,我们可以使用反射去进行调用.

使用反射就可以在当前单例模式中,创建出多个实例.但是,反射属于"非常规"的编程手段.正常开发的时候不应该使用/慎重使用,滥用反射,会带来极大地风险,会让代码变得比较抽象,难以维护.(当然了,Java中也有其他方式来实现单例模式,不怕反射的那种(选择性了解即可))

这个代码的执行时机,是在Singleton类被jvm加载的时候,Singlenton类会在jvm第一次使用的时候被加载,也不一定是程序启动的时候.

懒汉模式(延时)

在第一次使用实例的时候再创建,否则能不创建就不创建.

懒汉与饿汉,谁是线程安全的

那么,正题来了:懒汉模式和饿汉模式,谁是线程安全的?

饿汉模式,在多线程中,读取Singleton的内容,属于是多个线程读取同一个变量,这个是没问题的.但是对于懒汉模式来说,就不一样了,虽然在创建对象的过程中,我们可以创造出多个对象,虽然编译器的回收机制会帮我们回收由一个引用指向多个对象的这种情况的其他的没有引用的对象,虽然最后的结果仍然是一个对象,但是,有的时候创建一个对象的开销是非常大的.

加锁是一个成本比较高的操作,加锁可能会引起阻塞等待.加锁的基本原则是:非必要,不加锁,不能无脑加锁.如果无脑加锁,就会导致程序执行效率受到影响.(比如我们在数据结构当中学习的集合类,Vector,HashTable,StringBuffer...这些都是在关键方法上写了synchronized.无论是单线程使用还是多线程使用,无论是否场景存在线程安全问题,都是会加锁的)

那么我们如何解决懒汉模式下的线程安全问题?

我们可以想象, 当多个线程频繁的去判断'if(singlenton==null)'的时候,就可能会发生内存可见性问题,我们为了避免这种情况发生,所以我们给 singlenton 用 volatile 修饰了.

这里到底会不会发生内存可见性问题,我们只是分析了一下,可能会存在这样的风险,编译器实际上在这个场景中是否会发生优化,还是要打上问号的.这里的代码和前面内存可见性的代码有本质的区别的,前面内存可见性的代码是一个线程反复读,在这里是多个线程反复读,是否会触发编译器的优化?这个不好说,但是加上 volatile 是更稳妥的做法.此外加上 volatile 可以防止编译器进行指令重排序.

指令重排序

指令重排序:也是编译器优化的一种手段,保证原有执行逻辑不变的情况下,对代码顺序进行调整,使调整之后的执行效率提高.

.针对不完整的对象使用属性/方法就不保证出现什么情况了

给 instance 加上 volatile 之后,此时针对 instance 进行的赋值操作,就不会产生上述的指令重排序了.必须按照 1 2 3 的顺序执行,不会出现 1 3 2.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值