设计模式-单例模式(Singleton Pattern)
本文由@呆代待殆原创,转载请注明出处。
单例模式简述
单例模式保证了我们的类只有一个实例,并且我们在任何时候都可以取得这个实例,其中保证我们的类有且仅有一个实例在某些时候是相当重要的事情,比如我们只需要一个线程池而不是两个等等,但是我们也要注意,单例模式适用的情况比我们想象中的要少,所以请不要滥用这个模式。
单例模式具有的一些特征
1,单例模式保证了我们的程序中有且仅有一个实例的存在。
2,我们在任何时候都能取得这个实例。
3,单例模式的构造方法是私有的,所以在不破坏这一私有条件的情况下单例类是不能作为父类存在的。
单例模式的定义与基本结构
单例模式只有一个类而已,所以实际上并不存在结构这一说= =,但是我们还是可以看一下它的定义。
定义:确保类有且仅有一个实例,并保证任何时候都能访问这个实例。(这句话好像已经出现了好的次 = =)
单例模式的定义与结构都非常简单,理解起来甚至不需要举额外的例子,但是,真正去实现单例的时候我们还是有很多细节要注意的,那么下面我们就在实际的代码中继续研究吧。
单例模式的代码实现(Java版)
代码实现
1 public class MySingleten { 2 public static MySingleten instance=null;//指向实例的变量 3 private MySingleten(){}//私有化构造函数,然别的代码无法创建这个类的实例 4 public static MySingleten getInstance(){//我们取得这个单例的方法。 5 if(null==instance){ 6 instance=new MySingleten(); 7 } 8 return instance; 9 } 10 public void MyFunction(){//一般方法的代表 11 System.out.println("我是单例,这是我的方法"); 12 } 13 }
这种实现方式我们一般叫它:懒汉式
为什么呢?因为它直到第一次被调用的时候才会生成自己的实例(就像我们每次都要到交作业的时候才会开始写作业一样,总之就是懒呗= ̄ω ̄=)
注意:懒汉式是线程不安全的
举例:想象一下情况,线程A执行到代码的第5行,判断成功,在准备进入第6行的时候CPU切换了,线程B恰好也执行这段代码,这时很明显instance还是==null的浴室线程B成功创建了一个instance的实例,结果,当CPU又切回线程A时,麻烦来了,线程A继续执行第6行代码又创建了一个instance的实例,并把原来的那个覆盖了,这就很有可能导致意想不到的问题。
所以,我们必须想办法解决这个问题,这里我们提供以下几种思路。
饿汉式:不再等到要使用的时候才创建,而是在程序开始的时候就创建好(对这个实例很饥渴的样子,所以叫饿(chi)汉式)
1 public class MySingleten { 2 public static MySingleten instance=new MySingleten();//一开始就生成这个变量就不存在线程问题了 3 private MySingleten(){} 4 public static MySingleten getInstance(){ 5 return instance; 6 } 7 public void MyFunction(){ 8 System.out.println("我是单例,这是我的方法"); 9 } 10 }
synchronized方法:直接在懒汉式的getInstance方法前加上synchronized修饰符,这样就能解决线程安全问题了,这个解决方法是最简单的,但是效率却非常低下,因为只有第一次创建实例的时候这个synchronized是有必要的,当实例创建完成后,这个synchronized就只剩下拖慢速度的作用了。
1 public class MySingleten { 2 public static MySingleten instance=null; 3 private MySingleten(){} 4 public static synchronized MySingleten getInstance(){ 5 if(null==instance){ 6 instance=new MySingleten(); 7 } 8 return instance; 9 } 10 public void MyFunction(){ 11 System.out.println("我是单例,这是我的方法"); 12 } 13 }
双重加锁方法:在懒汉式的基础上,我们可以用两把锁来分别控制单例的取得和创建,因为只需要在第一次创建单例的时候注意线程安全问题,那么,我们在内层锁上用synchronized来控制,在外层锁上用 if(null==instance) 来判断是否存在这个实例,这样就省去了synchronized在后来浪费的同步时间
1 public class MySingleten { 2 public volatile static MySingleten instance=null;//注意这里增加了volatile关键字 3 private MySingleten() {} 4 public static MySingleten getInstance() { 5 if (null == instance) {// 外层锁,判断是否实例已经被创建 6 synchronized (MySingleten.class) {// 内层锁控制线程间同步,实例被创建后就没有运行的机会了,省去了多余的线程间同步成本 7 if(null==instance)//需要再次检查,因为很有可能线程A在这里时,线程B已经通过外层的if了。 8 instance = new MySingleten(); 9 } 10 } 11 return instance; 12 } 13 public void MyFunction() { 14 System.out.println("我是单例,这是我的方法"); 15 } 16 }
静态全局变量和单例模式的对比
1,静态全局变量并不能保证对象是唯一的(既然你能创建这个静态全局变量就说明这个类的构造函数并不是私有的)。
2,多余的全局变量会造成命名空间的污染。
3,全局变量总是存在,会一直占用内存,而单例模式可实现访问的时候再创建单例的实例。
4,单例模式产生的对象保存在堆里,但是静态全局变量保存在栈里。
静态成员和单例模式的对比
1,用都是静态成员的类去模拟单例的话,它是不能实现别的接口的,这种用法脱离了面向对象的思想(除非这个类的应用与实现不需要面向对象的思想那么你可以这么做)。
2,静态成员可以选择性的将类里面的东西分成需要保证唯一性的和不需要保证唯一性的,某些时候更加灵活。
java版本兼容性提醒
1,Java1.2之前的垃圾回收机制是有bug的,会造成当单例实例在没有全局引用的情况下被清除掉。
2,Java1.4之前许多JVM对于volatile的实现会导致双重加锁的方法失效
关于单例模式的一些争议
如果你刚刚看完了我写的博文并觉得又有了一点收获而感到很开心的话(如果真的是这样那我也会很开心的♪(^∇^*)),我觉得你可以先冷静一下(大雾= =),单例设计模式在网络上是一个很有争议的模式,有人觉得这个模式违反了太多的设计原则,有人觉他增加了代码的耦合性等等,但有的时候我们又确实需要单例带给我们的一些特性,本来博主想多看一些别人的文章后帮大家总结一下网络上关于这方面的讨论,结果博主发现这些讨论实在是太多了,而且比较杂,各种声音都有,stackoverflow上有个类似的问题以"这个问题的回答是基于个人选择而不是基于经验的..."而被关闭了,所以博主觉得还是选一些博主觉得比较好的连接给各位,让各位自行斟酌比较好。(相关讨论的连接都放在了参考资料里,如果觉得不够的话,可以直接google一下"singleton why bad")
参考资料:
1,《Head First 设计模式》
2,https://agiletribe.wordpress.com/2013/10/08/dont-abuse-singleton-pattern/ 关于何时才应该使用单例模式
3,https://www.cnblogs.com/seesea125/archive/2012/04/05/2433463.html关于为什么不用静态方法而要用单例模式
4,https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons关于单例模式争议的讨论
5,https://stackoverflow.com/questions/519520/difference-between-static-class-and-singleton-pattern关于单例模式和静态类之间的讨论