距离上一篇文章已经过去两个月了。。。最近突然醒悟自己怎么越来越懒了,每天就打游戏堕落了哈哈哈
最近重新拾起Head First 设计模式,继续开始进阶之路
今天要写的是单件模式(书上是第5章才开始写着单件(不过我听到更多的是单例),放在这里是我觉得比较简单,这也是所有设计模式中最简单的,不过还是不能小看,因为在实现上还是有许多的波折。下面就直接开始!
问题来了: 如何实现一个唯一的对象?这有什么作用?
大佬:有一些对象其实我们只需要实现一个,比如:线程池(threadpool),缓存(cache),对话框,设置注册表的对象,日志对象等。事实上,这些类对象只能有一个实例,如果创造多个实例,就会导致许多问题发生,例如:程序的行为异常、资源使用过量,或是导致不一致的结果。
我:说的有道理,确实有一些只需要一个实例,但这需要整个章节来讲吗,难道不能靠程序员之间约定或是利用全局变量做到?我觉得利用java的静态变量就可以做到。
大佬:许多时候,的确通过程序员之间的约定就可以办到,但如果有更好的办法,大家也乐意接受,就跟其他的模式一样,单件模式也是经得起时间的考验的,可以确保只有一个实例被创建。单件模式也给我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。
我:什么缺点?
大佬:举例来说,如果将一个对象赋值给全局变量,那你必须要在程序一开始就创建好对象,对吧?万一这个对象非常耗费资源,而程序这次的执行过程中又一直没用到它,不就浪费了吗,稍后你会看到,利用单件模式,我们可以在需要时才创建对象。
我:我觉得没什么困难的。
大佬:利用静态类变量,静态方法和适当的访问修饰符,可以做到这一点,但是,不管使用哪一种方法,能够了解单件的运作方式仍然是很有趣的事,单件模式听起来简单,要做得对可不简单。
问题:如何保证一个对象只能被实例化一次?
如何创建一个对象? new MyObject();
万一另一个对象想创建MyObject会怎样?可以再次new 吗? 当然可以了。
所以,一旦有一个类,我们是否能够多次的实例化它? 如果是公开的类,就可以。
如果不是的话呢? 如果不是公开类,只有同一个包内的类可以实例化它,但依然可以实例化它很多次。
你知道可以这样做吗?
public class MyObject {
private MyObject() {}
}复制代码
有可以使用私有的构造器的对象吗? 有的,在MyObject内的代码是唯一可以调用此构造器的代码。
这时在这个类里面加一个getInstance()方法
public class MyObject {
public static MyObject getInstance(){
return new MyObject();
}
private MyObject() {
}
}复制代码
这样就可以在其他包实例化该对象了。
现在就需要保证它只能有一个实例被产生了。修改代码
public class MyObject {
private static MyObject myObject;
public static MyObject getInstance(){
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}
private MyObject() {
getInstance();
}
}复制代码
这样 ,我们就可以实现延迟加载化了。
此时,有另外一个问题出现了,突然有两条线程同时执行了实例化方法,并且创造了两个同样的对象。导致程序执行错误。
处理多线程
只要把getInstance变成同步(synchronized)方法,多线程灾难几乎就可以轻易的解决。
public static synchronized MyObject getInstance(){
if (myObject == null) {
myObject = new MyObject();
}
return myObject;
}复制代码
如果不知道synchronized关键字的方法,可以自行了解一下。
这样确实可以解决问题,但是会减低性能,这不就产生了另一个问题了吗,并且只有第一次执行此方法的时候,才需要真正的同步,换句话说,一旦设置好了myObject对象,就不再需要同步这个方法了。之后每次调用这个方法,同步是个累赘。
改善多线程
1、如果getInstance()的性能对应用程序不是很关键,就什么都别做。
2、使用”急切”创建实例,而不是延迟实例化的做法。
如果应用程序总是创建并使用单例实例,或者在创建和运行方面的负担不太繁重,你可能想要在程序启动时就创建此单件
private static MyObject myObject = new MyObject();
public static MyObject getInstance(){
return myObject;
}
private MyObject() {
}复制代码
3、用“双重检查加锁”,在getInstance()中减少使用同步
利用双重检查加锁(double-checked locking) ,首先检查实例是否已经创建了,如果尚未创建,才进行同步,这样一来就只有第一次会同步,这正是我们真正想要的。
private volatile static MyObject myObject;
public static MyObject getInstance(){
if (myObject == null) {
synchronized (MyObject.class){
if (myObject == null) {
myObject = new MyObject();
}
}
}
return myObject;
}
private MyObject() {}复制代码
volatile 关键字确保当myObject被初始化成实例后,多个线程正确的处理它。复制代码
如果性能是你关心的重点,这个做法可以帮你大大的减少getInstance()的时间耗费。
总结
单件模式(单例) : 确保一个类只有一个实例,并提供全局访问点。