从源码角度看java设计模式之单例模式
单例设计模式(Singleton Pattern)是最简单且常见的设计模式之一,主要作用是提供一个全局访问且只实例化一次的对象,避免多实例对象的情况下引起逻辑性错误(实例化数量可控)…
概述
Java中,单例模式主要分三种:懒汉式单例、饿汉式单例、登记式单例三种。
特点
案例
在这里我推荐下我自己的JAVA学习扣扣群:943111692,不管你是小白还是大牛,小编我都挺欢迎,不定期分享干货,包括我自己整理的一份最新JAVA资料和零基础入门教程!,欢迎初学和进阶中的小伙伴。
注意事项
解锁姿势
第一种:单一检查(懒汉)非线程安全
日志
分析: 在单线程环境一切正常,balancer1和balancer2两个对象的hashCode一模一样,由此可以判断出堆栈中只有一份内容,不过该代码块中存在线程安全隐患,因为缺乏竞争条件,多线程环境资源竞争的时候就显得不太乐观了,请看上文代码注释内容
第二种:无脑上锁(懒汉)线程安全,性能较差,第一种升级版
分析: 毫无疑问,知道synchronized关键字的都知道,同步方法在锁没释放之前,其它线程都在排队候着呢,想不安全都不行啊,但在安全的同时,性能方面就显得短板了,我就初始化一次,你丫的每次来都上个锁,不累的吗(没关系,它是为了第三种做铺垫的)..
第三种:双重检查锁(DCL),完全就是前两种的结合体啊,有木有,只是将同步方法升级成了同步代码块
1.假设new LazyLoadBalancer()加载内容过多
2.因重排而导致loadBalancer提前不为空
3.正好被其它线程观察到对象非空直接返回使用
存在问题: 首先我们一定要清楚,DCL是不能保证线程安全的,稍微了解过JVM的就清楚,对比C/C++它始终缺少一个正式的内存模型,所以为了提升性能,它还会做一次指令重排操作,这个时候就会导致loadBalancer提前不为空,正好被其它线程观察到对象非空直接返回使用(但实际还有部分内容没加载完成)
解决方案: 用volatile修饰loadBalancer,因为volatile修饰的成员变量可以确保多个线程都能够顺序处理,它会屏蔽JVM指令重排带来的性能优化。
第四种:Demand Holder (懒汉)线程安全,推荐使用
分析: 在Demand Holder中,我们在LazyLoadBalancer里增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,由于静态单例对象没有作为LazyLoadBalancer的成员变量直接实例化,类加载时并不会实例化LoadBalancerHolder,因此既可以实现延迟加载,又可以保证线程安全,不影响系统性能(居家旅行必备良药啊)
第五种:枚举特性(懒汉)线程安全,推荐使用
分析: 相比上一种,该方式同样是用到了JAVA特性:枚举类保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量)
第六种:饿汉单例(天生线程安全),
分析: 利用ClassLoad机制,在加载时进行实例化,同时静态方法只在编译期间执行一次初始化,也就只有一个对象。使用的时候已被初始化完毕可以直接调用,但是相比懒汉模式,它在使用的时候速度最快,但这玩意就像自己挖的坑哭着也得跳,你不用也得初始化一份在内存中占个坑…