浅谈设计模式6——单件模式(单例模式)
序
今天本来实在想再往下看点书,但是必将抽象模式和单例模式都没有总结,有点狗熊劈棒子的感觉。以前就一直当狗熊,这次说什么也得要温故知新下。不然恶习累积,最终受害者还是自己,搞得一事无成。所以希望这次也算是一个新的起点,让自己养成一个好习惯——读完了书能写写,复习复习。当然,还能跟大家一起分享一下。何乐不为?
以下就仔细说说单例模式,这个模式还是挺简单的——相比较于前面说的异常混乱的工厂模式,怎么看都让人一头雾水。它虽然简单,但万不可小觑,否则刚刚吃了餐前点心,大餐绝对就要错过了。
1、背景
单例模式的背景比较简单。比如对于PC机来说,通常情况下只有一个显示器,一套键盘鼠标。所以如果对于显示器类,或者鼠标类,只能构造一个类实体——对象。看上去感觉很浪费资源——一个类只有一个实体。但是,由于类封装了属性和方法,因此用户对实体操作起来讲更加方便,尤其是对于操作系统这种庞然大物,如果不做好封装,将会变得异常凌乱。
另一方面,当某个对象创建时,十分消耗软硬件资源,那就不如什么时候用到它,什么时候再创建——对象的延迟创建(lazy)。
根据上面你的分析,我们引出单例模式。
2、单例模式
由于单例模式规定一个类只能创建一个实体,因此就需要控制类的构造函数。而由于只有一个实体,因此,这个实体是属于类的。这样说看起来比较抽象,那看下代码,相信就能明白不少。
class Single
{
public static Single single=null;
private Single() {
}
public static Single getInstance()
{
if(single==null)
single=new Single();
return single;
}
}
这个单例模式类定义起来很简单。那么具体说下为什么会这么定义:
1)由于该类只拥有一个实例,即实例和类一一对应,因此将single变量定义成静态的共有变量。但是,有个问题,在类的定义中,定义自身的实体是否合法。这个问题大家可以类比C语言中的结构,可以在结构中定义其自身的指针。而java中定义对象也是这个道理,我定义的只是一个指针,而没有定义实体。但是下面的代码是非法的,因为这样做会造成无限循环:
class A
{
public A a=new A();
public A(){}
}
2)因为在创建Single的对象之前,只有Single类被定义了,因此getInstance()方法也要定义为static方法。
3、关于多线程
一旦出现多线程编程就相对来说麻烦很多。为什么这么说?因为两个线程可能同时访问单例模式类中的getInstance()方法,假设两个线程都执行if(single==null),第一个线程检测到没有创建实例,而第二个线程也没有检测到创建实例。那么就会产生:两个线程同时创建实例的情况。显然,单例模式在此处就失败了。那么怎么办?下面有三个方法:
1)将getInstance()方法声明为同步方法:
public static synchronized Single getInstance()
{
if(single==null)
single=new Single();
return single;
}
这样做依然有个问题,就是所有的线程都要一个一个来做,显然效率上较低。如果系统可以容忍提前创建对象的开销,那么我们可以试试第二种方法。
2)该方法直接创建一个对象:
class Single
{
public static Single single=new Single();
private Single() {
}
public static Single getInstance()
{
return single;
}
}
注意到在声明静态对象的时候直接创建了实体,这样无论什么时候,所有线程访问的都是最开始类定义时候,产生的实体对象。不过效率上多少还是不尽如人意。那再看下第三种方法,该方法仅在5.0以上版本中适用。
3)该方法使用了关键字volatile
class Single
{
public volatile static Single single=new Single();
private Single() {
}
public static Single getInstance()
{
if(single==null)
synchronized (Single.class) {
if(single==null)
single=new Single();
}
return single;
}
}
volatile为编译关键字,表示被修饰的变量是不稳定的,需要在某些情况下受到保护。
getInstance()方法首先检测是否创建了对象,若是没创建对象,阻塞,进行进程同步,然后再检测,当确实是空的,再创建对象。否则后面的代码就不会执行了。
上面的方法保证了,当有一堆线程发现对象没有创建,则开始进行同步,而一旦对象被创建了,后续的线程就不会再被阻塞而进行线程同步。相对于第一种方法——只要检测对象是否被创建,就需要同步。要好很多。
至此,单例模式结束。
一块心病总算了解,可以继续往下看书了。。。哦哈哈。。。