深入灵魂的考验,每行注释都是灵魂的单例模式,源码 + 实例降临
不管是设计模式也好,别的模式也要,他都是为了解决问题而发明的有效的方法。除了我们已经熟悉的23种设计模式以外,还有MVVM、Combinator等其它的东西,都已经是前辈们经过多年的摸爬滚打总结出来的,其有效性不容置疑。我这篇文章也不会用来证明设计模式是有用的,因为在我看来,这就跟1+1=2一样明显(在黑板上写下1+1=2)
而这,在现在这个追求高质量代码的时代,虽然显得有一些复杂,但是我个人还是“推崇”(看好了,我有引号的)这个东西,毕竟面试必问系列,你咋整
来看今天的内容吧,有代码,有实例,并且有一些内容我直接放在代码中通过注释进行讲解,会更好理解
文章首发公众号:Java架构师联盟
一、设计部分:单例的实现思想、代码及注意问题
二、应用部分:单例的适用场景
优点:
第一、能减少资源的使用,但有时需要通过线程同步来控制资源的并发访问;也避免对共享资源的多重占用
第二、控制实例产生的数量(允许可变数目的实例),由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
第三、作为通信媒介使用,也就是数据共享,共享这一个对象一个实例(如线程池),它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信,但注意多线程同步问题。
缺点:
1.不太适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态,所以就算保存了,需要加入同步机制来避免错误。2.由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。3.单例类的职责过重,在一定程度上违背了“单一职责原则”。4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
使用注意事项:
1.使用时不能用反射模式创建单例,否则会实例化一个新的对象2.使用懒单例模式时注意线程安全问题3.饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
适合场景:
1、有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
3、频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;
4、单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
具体应用场景举例:
外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件,在我们日常使用的在Windows中也有不少单例模式设计的组件,象常用的文件管理器。由于Windows操作系统是一个典型的多进程多线程系统,那么在创建或者删除某个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象。采用单例模式设计的文件管理器就可以完美的解决这个问题,所有的文件操作都必须通过唯一的实例进行,这样就不会产生混乱的现象。
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
数据库连接池的设计一般采用单例模式,数据库连接是一种数据库资源。软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的。当然,使用数据库连接池还有很多其它的好处,可以屏蔽不同数据数据库之间的差异,实现系统对数据库的低度耦合,也可以被多个系统同时使用,具有高可复用性,还能方便对数据库连接的管理等等。数据库连接池属于重量级资源,一个应用中只需要保留一份即可,既节省了资源又方便管理。所以数据库连接池采用单例模式进行设计会是一个非常好的选择。
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
spring的bean(scope)默认是single,当然也可以当然也可以设置为prototype,比如struts2的action就必须是prototype,因为请求不同,一个请求对应一个action对象。
我们知道单例会发生线程安全问题,那么spring是怎么来解决的呢?
问题:当Bean对象对应的类存在可变的成员变量并且其中存在改变这个变量的线程时,多线程操作该Bean对象时会出现线程安全。原因:当多线程中存在线程改变了bean对象的可变成员变量时,其他线程无法访问该bean对象的初始状态,从而造成数据错乱解决方式:1.在Bean对象中尽量避免定义可变的成员变量;2.在bean对象中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中
2个具体场景案例
1、网站在线人数统计;
其实就是全局计数器,也就是说所有用户在相同的时刻获取到的在线人数数量都是一致的。要实现这个需求,计数器就要全局唯一,也就正好可以用单例模式来实现。当然这里不包括分布式场景,因为计数是存在内存中的,并且还要保证线程安全。下面代码是一个简单的计数器实现。
1、配置文件访问类;
项目中经常需要一些环境相关的配置文件,比如短信通知相关的、邮件相关的。比如 properties 文件,这里就以读取一个properties 文件配置为例,如果你使用的 Spring ,可以用 @PropertySource 注解实现,默认就是单例模式。如果不用单例的话,每次都要 new 对象,每次都要重新读一遍配置文件,很影响性能,如果用单例模式,则只需要读取一遍就好了。以下是文件访问单例类简单实现:
版权声明: 本文为 InfoQ 作者【小Q】的原创文章。
原文链接:【http://xie.infoq.cn/article/929072b3dca9303738e0a3d28】。
本文遵守【CC BY-NC-ND】协议,转载请保留原文出处及本版权声明。
评论