发布于2021-05-29 22:34 阅读(660) 评论(0) 点赞(28) 收藏(0)
[版权申明] 非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/117266347
出自:shusheng007
设计模式汇总篇,一定要点赞收藏:
单例模式是GOF 23种设计模式中最简单的一种,但是使用却一点不少,上帝果然喜欢简洁。单例模式虽然简单,但是也还是有很多可以探讨的地方。
咱们就来聊聊Java中单例模式的5种写法吧,以及各种设计模式的优劣,最后讨论一下你喜欢哪一种,为什么?
创建型(creational)
1颗星
某个类只有一个实例,且自行实例化并向整个系统提供此实例
你是不是觉的终于有一个可以看得懂的设计模式的定义啦?如果是这样,说明你原本对它就比较熟悉。
当你希望整个系统运行期间某个类只有一个实例时候
这里有一张图,看见了吗?图在你心中
楚中天(外号林蛋大)在外包公司干了快两年了,这期间都是外派王二狗公司的。两年中蛋大总有种寄人篱下的感觉,这不王二狗看蛋大表现很好,就向公司推荐将其转为正式。公司会通过面试来对新员工定级,下面是他们之间的对话:
面试官:林蛋大,是叫林蛋大吧?
楚中天:内心活动:蛋大nmb,瞎吗?小学毕业了吗?老子叫楚中天!脱口而出:老师,其实我叫楚中天,您可以叫我中天。
面试官:哦,好的,蛋大。你能谈谈单例模式吗,你能用几种方法实现单例模式,他们之间都有什么利弊吗?
楚中天:balabala…
多线程环境下如何保证系统中只有一个实例?类实现序列化时如何保证?如何保证不能通过反射创建新的实例?
这块又分为懒汉模式与饿汉模式。
其实也很好理解,懒汉的意思就是这个类很懒,只要别人不找它要实例,它都懒得创建。饿汉模式正好相反,这个类很着急,非常饥渴的要得到自己的实例,所以一有机会他就创建了自己的实例,不管别人要不要。
这个简单粗暴,在类加载时候就创建了实例,属于懒汉模式。其是线程安全的,这一点由JVM来保证,但是有一个缺点,可以通过反射创建新的实例。如果让你改进,你怎么弄呢?评论区留下你的见解
public class Singleton1 {
private final static Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
}
public static Singleton1 getInstance(){
return INSTANCE;
}
}
使用这个写法的程序员应该说水平不是太高,这种写法应该被抛弃。其不是线程安全的,也就是说在多线程环境下,系统中有可能存在多个实例。除此之外,和上面一样通过反射也可以创建新的实例。
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {
}
public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
针对线程不安全问题,让我们尝试改进一下它:
我们将实例化过程放到同步块里可以解决问题吗?如下所示
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
instance = new Singleton2();
}
}
return instance;
}
很可惜,这种方式也是不行的,让我们简单分析一下为什么不行。
假设我们有两个线程 T1与T2并发访问getInstance
方法。当T1执行完if (instance == null)
且instance为null时,其CUP执行时间被T2抢占,所以T1还没有创建实例。T2也执行if (instance == null)
,此时instance肯定还为null,T2执行创建实例的代码,当T1再次获得CPU执行时间后,其从synchronized
处恢复,又会创建一个实例。
那我们之间将同步基本升级到获取实例的方法基本可以吗?恭喜你,可以!但是,又是该死的但是!但是程序的性能被极大的降低了。下面的Singleton3
给获取实例的方法添加了synchronized
。这样的话,线程是安全了,但是却极大的降低了性能,因为大部分情况下线程都只是去获取这个实例,但现在却要排队。
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
那有没有既不降低性能又保证线程安全的方法呢?Java5后答案是肯定的,因为现在Java5已经绝迹江湖了,所以可以说答案是肯定的。
为了解决上面单null检查的线程安全与程序性能的问题,出现了double-check的方式。此方式的关键一个点就在于volatile
关键字,其阻止了虚拟机指令重排,使得我们的双检查得以实现。在Java5之前,这种双重检查的方式即使加上了volatile
也没有用,还是不能用,因为JVM有bug。
所以double-check方式一定要加volatile
关键字,否则由于指令重拍会导致单例失败。关于volatitle
可以参考秒懂Java并发之volatile关键字引发的思考
public class Singleton4 {
private static volatile Singleton4 instance;
private Singleton4() {
}
public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
if(instance ==null){
instance = new Singleton4();
}
}
}
return instance;
}
}
我最开始接触到这种double-check的写法时觉得很奇诡,为什么要搞那么多check?后来我明白了:
第一重check为了提高访问性能。因为一旦实例被创建,所有的check永远为假。其实你把第一重check去掉也没问题,只是访问性能降低了,那样就变成和直接同步方法一样了。
第二重check是为了线程安全,确保多线程环境下只生成一个实例。具体分析可以参考单check部分。第一重ckeck可以被多个线程进入,但是第二重check却只能排队进入
这种方式其实很棒,既是线程安全的,也是懒汉式的,那个实例只有在你首次访问时候才会生成。我们完全可以使用这种方式替换double-check方式。
public class Singleton5 {
private Singleton5() {
}
private static class SingletonInstance {
private final static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance() {
return SingletonInstance.INSTANCE;
}
}
最牛逼的其实是这哥们儿,以上所有方式均存在一个问题,即通过反射的方式可以创建多个实例。如果你的类实现了序列化,那还要防止序列化生成多个实例的问题。而枚举保证了线程安全,保证了反射安全,保证了序列化…
但是,但是,但是实际项目中却很少有人用enum来实现单例…
public enum Singleton6 {
INSTANCE;
}
5种实现单例模式的方式已经聊完了,除了使用枚举,你有办法防止反射破坏单例这个问题吗?小伙伴们踊跃发言,留言区咱们讨论一下
GitHub源码地址:design-patterns
作者:我爱编程
链接:http://www.javaheidong.com/blog/article/207651/8751bab179b2d4126d00/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!