`

传统Singleton模式与改进单例模式

 
阅读更多

定义: Singleton模式主要作用是保证在Java应用程序中,一个Class只有一个实例存在。  一个实例表示是单线程,在很多操作中,比如建立目录 数据库连接都需要单线程操作,Singleton模式经常用于控制对系统资源的控制,我们常常看到工厂模式中工厂方法也用Singleton模式实现的.

如何使用? 一般Singleton模式通常有几种形式:

 

方式一:(饿汉式)

public class Singleton {  
  private static Singleton _instance = new Singleton();  
  public static Singleton getInstance() {  
    return _instance;  
  }  
}  

//调用方法: 
//Singleton.getInstance() 

 

方式二:(懒汉式)

 

public class Singleton {  
  private static Singleton _instance = null;  
  public static Singleton getInstance() {  
    if (_instance==null) 
      _instancenew Singleton()  
    return _instance;  
  }  
}  
//调用方法: 
//Singleton.getInstance()

 

Singleton模式在创建对象时使用较多,使用它也比较简单。

 

认识单例模式

    (1)单例模式的功能

单例模式的功能是用来保证这个类在运行期间只会被创建一个类实例,另外单例模式还提供了一个全局唯

一访问这个类实例的访问点,就是那个getInstance的方法。不管采用懒汉式还是饿汉式的实现方式,这个全局

访问点是一样的。

对于单例模式而言,不管采用何种实现方式,它都是只关心类实例的创建问题,并不关心具体的业务功

能。

(2)单例模式的范围

也就是在多大范围内是单例呢?

观察上面的实现可以知道,目前Java里面实现的单例是一个ClassLoader及其子ClassLoader的范围。因为

一个ClassLoader在装载饿汉式实现的单例类的时候就会创建一个类的实例。

这就意味着如果一个虚拟机里面有很多个ClassLoader,而且这些ClassLoader都装载某个类的话,就算这

个类是单例,它也会产生很多个实例。当然,如果一个机器上有多个虚拟机,那么每个虚拟机里面都应该至少

有一个这个类的实例,也就是说整个机器上就有很多个实例,更不会是单例了。

另外请注意一点,这里讨论的单例模式并不适用于集群环境,对于集群环境下的单例这里不去讨论,那不

属于这里的内容范围。

(3)单例模式的命名

一般建议单例模式的方法命名为:getInstance(),这个方法的返回类型肯定是单例类的类型了。

getInstance方法可以有参数,这些参数可能是创建类实例所需要的参数,当然,大多数情况下是不需要的。

单例模式的名称:单例、单件、单体等等,翻译的不同,都是指的同一个模式。

 

 

懒汉式和饿汉式实现

前面提到了单例模式有两种典型的解决方案,一种叫懒汉式,一种叫饿汉式,这两种方式究竟是如何实现

的,下面分别来看看。为了看得更清晰一点,只是实现基本的单例控制部分,不再提供示例的属性和方法了;

而且暂时也不去考虑线程安全的问题,这个问题在后面会重点分析。

1:第一种方案 懒汉式

(1)私有化构造方法

要想在运行期间控制某一个类的实例只有一个,那首先的任务就是要控制创建实例的地方,也就是不能随

随便便就可以创建类实例,否则就无法控制创建的实例个数了。现在是让使用类的地方来创建类实例,也就是

http://chjavach.javaeye.com 1.7 研磨设计模式之单例模式-2

第 42 / 186 页

在类外部来创建类实例。

那么怎样才能让类的外部不能创建一个类的实例呢?很简单,私有化构造方法就可以了!示例代码如下:

private Singleton(){

}

(2)提供获取实例的方法

构造方法被私有化了,外部使用这个类的地方不干了,外部创建不了类实例就没有办法调用这个对象的方

法,就实现不了功能处理,这可不行。经过思考,单例模式决定让这个类提供一个方法来返回类的实例,好让

外面使用。示例代码如下:

public Singleton getInstance(){

}

(3)把获取实例的方法变成静态的

又有新的问题了,获取对象实例的这个方法是个实例方法,也就是说客户端要想调用这个方法,需要先得

到类实例,然后才可以调用,可是这个方法就是为了得到类实例,这样一来不就形成一个死循环了吗?这不就

是典型的“先有鸡还是先有蛋的问题”嘛。

解决方法也很简单,在方法上加上static,这样就可以直接通过类来调用这个方法,而不需要先得到类实例

了,示例代码如下:

public static Singleton getInstance(){

}

(4)定义存储实例的属性

方法定义好了,那么方法内部如何实现呢?如果直接创建实例并返回,这样行不行呢?示例代码如下

public static Singleton getInstance(){

return new Singleton();

}

当然不行了,如果每次客户端访问都这样直接new一个实例,那肯定会有多个实例,根本实现不了单例的

功能。

怎么办呢?单例模式想到了一个办法,那就是用一个属性来记录自己创建好的类实例,当第一次创建过

后,就把这个实例保存下来,以后就可以复用这个实例,而不是重复创建对象实例了。示例代码如下:

http://chjavach.javaeye.com 1.7 研磨设计模式之单例模式-2

第 43 / 186 页

private Singleton instance = null;

(5)把这个属性也定义成静态的

这个属性变量应该在什么地方用呢?肯定是第一次创建类实例的地方,也就是在前面那个返回对象实例的

静态方法里面使用。

由于要在一个静态方法里面使用,所以这个属性被迫成为一个类变量,要强制加上static,也就是说,这里

并没有使用static的特性。示例代码如下:

private static Singleton instance = null;

(6)实现控制实例的创建

现在应该到getInstance方法里面实现控制实例创建了,控制的方式很简单,只要先判断一下,是否已经

创建过实例了。如何判断?那就看存放实例的属性是否有值,如果有值,说明已经创建过了,如果没有值,那

就是应该创建一个,示例代码如下:

public static Singleton getInstance(){

//先判断instance是否有值

if(instance == null){

//如果没有值,说明还没有创建过实例,那就创建一个

//并把这个实例设置给instance

instance = new Singleton ();

}

//如果有值,或者是创建了值,那就直接使用

return instance;

}

(7)完整的实现

至此,成功解决了:在运行期间,控制某个类只被创建一个实例的要求。完整的代码如下,为了大家好理

解,用注释标示了代码的先后顺序,示例代码如下:

public class Singleton {

//4:定义一个变量来存储创建好的类实例

//5:因为这个变量要在静态方法中使用,所以需要加上static修饰

private static Singleton instance = null;

http://chjavach.javaeye.com 1.7 研磨设计模式之单例模式-2

第 44 / 186 页

//1:私有化构造方法,好在内部控制创建实例的数目

private Singleton(){

}

//2:定义一个方法来为客户端提供类实例

//3:这个方法需要定义成类方法,也就是要加static

public static Singleton getInstance(){

//6:判断存储实例的变量是否有值

if(instance == null){

//6.1:如果没有,就创建一个类实例,并把值赋值给存储类实例的变量

instance = new Singleton();

}

//6.2:如果有值,那就直接使用

return instance;

}

}

2:第二种方案 饿汉式

这种方案跟第一种方案相比,前面的私有化构造方法,提供静态的getInstance方法来返回实例等步骤都

一样。差别在如何实现getInstance方法,在这个地方,单例模式还想到了另外一种方法来实现getInstance方

法。

不就是要控制只创造一个实例吗?那么有没有什么现成的解决办法呢?很快,单例模式回忆起了Java中

static的特性:

• static变量在类装载的时候进行初始化

• 多个实例的static变量会共享同一块内存区域。

这就意味着,在Java中,static变量只会被初始化一次,就是在类装载的时候,而且多个实例都会共享这

个内存空间,这不就是单例模式要实现的功能吗?真是得来全不费功夫啊。根据这些知识,写出了第二种解决

方案的代码,示例代码如下:

public class Singleton {

//4:定义一个静态变量来存储创建好的类实例

//直接在这里创建类实例,只会创建一次

private static Singleton instance = new Singleton();

//1:私有化构造方法,好在内部控制创建实例的数目

private Singleton(){

}

 

//2:定义一个方法来为客户端提供类实例

//3:这个方法需要定义成类方法,也就是要加static

//这个方法里面就不需要控制代码了

public static Singleton getInstance(){

//5:直接使用已经创建好的实例

return instance;

}

}

注意一下,这个方案是用到了static的特性的,而第一个方案是没有用到的,因此两个方案的步骤会有一些

不同,在第一个方案里面,强制加上static也是算作一步的,而在这个方案里面,是主动加上static,就不单独

算作一步了。

所以在查看上面两种方案的代码的时候,仔细看看编号,顺着编号的顺序看,可以体会出两种方案的不一

样来。

不管是采用哪一种方式,在运行期间,都只会生成一个实例,而访问这些类的一个全局访问点,就是那个

静态的getInstance方法。

 

 

 

 

延迟加载的思想

单例模式的懒汉式实现方式体现了延迟加载的思想,什么是延迟加载呢?

通俗点说,就是一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过

去了才加载,所以也称Lazy Load,不是懒惰啊,是“延迟加载”,这在实际开发中是一种很常见的思想,尽可

能的节约资源。

 

 

 

缓存的思想

单例模式的懒汉式实现还体现了缓存的思想,缓存也是实际开发中非常常见的功能。

简单讲就是,如果某些资源或者数据会被频繁的使用,而这些资源或数据存储在系统外部,比如数据库、

硬盘文件等,那么每次操作这些数据的时候都从数据库或者硬盘上去获取,速度会很慢,会造成性能问题。

一个简单的解决方法就是:把这些数据缓存到内存里面,每次操作的时候,先到内存里面找,看有没有这

些数据,如果有,那么就直接使用,如果没有那么就获取它,并设置到缓存中,下一次访问的时候就可以直接

从内存中获取了。从而节省大量的时间,当然,缓存是一种典型的空间换时间的方案。

缓存在单例模式的实现中怎么体现的呢?

 

 

 

Java中缓存的基本实现

引申一下,看看在Java开发中的缓存的基本实现,在Java中最常见的一种实现缓存的方式就是使用Map,

基本的步骤是:

• 先到缓存里面查找,看看是否存在需要使用的数据

• 如果没有找到,那么就创建一个满足要求的数据,然后把这个数据设置回到缓存中,以备下次使用

• 如果找到了相应的数据,或者是创建了相应的数据,那就直接使用这个数据。

还是看看示例吧,示例代码如下:

 

 

 

/**
* Java中缓存的基本实现示例
*/
public class JavaCache {
/**
* 缓存数据的容器,定义成Map是方便访问,直接根据Key就可以获取Value了
* key选用String是为了简单,方便演示
*/
private Map<String,Object> map = new HashMap<String,Object>();
/**
* 从缓存中获取值
* @param key 设置时候的key值
* @return key对应的Value值
*/
public Object getValue(String key){
//先从缓存里面取值
Object obj = map.get(key);
//判断缓存里面是否有值
if(obj == null){
//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
//这里只是演示,所以直接写个假的值
obj = key+",value";
//把获取的值设置回到缓存里面
map.put(key, obj);
}
//如果有值了,就直接返回使用
return obj;
}
}
 

 

 

这里只是缓存的基本实现,还有很多功能都没有考虑,比如缓存的清除,缓存的同步等等。当然,Java的

缓存还有很多实现方式,也是非常复杂的,现在有很多专业的缓存框架,更多缓存的知识,这里就不再去讨论

了。

 

 

 

利用缓存来实现单例模式

其实应用Java缓存的知识,也可以变相实现Singleton模式,算是一个模拟实现吧。每次都先从缓存中取

值,只要创建一次对象实例过后,就设置了缓存的值,那么下次就不用再创建了。

虽然不是很标准的做法,但是同样可以实现单例模式的功能,为了简单,先不去考虑多线程的问题,示例

代码如下:

 

 

 

/**
* 使用缓存来模拟实现单例
*/
public class Singleton {
http://chjavach.javaeye.com 1.8 研磨设计模式之单例模式-3
第 50 / 186 页
/**
* 定义一个缺省的key值,用来标识在缓存中的存放
*/
private final static String DEFAULT_KEY = "One";
/**
* 缓存实例的容器
*/
private static Map<String,Singleton> map =
new HashMap<String,Singleton>();
/**
* 私有化构造方法
*/
private Singleton(){
//
}
public static Singleton getInstance(){
//先从缓存中获取
Singleton instance = (Singleton)map.get(DEFAULT_KEY);
//如果没有,就新建一个,然后设置回缓存中
if(instance==null){
instance = new Singleton();
map.put(DEFAULT_KEY, instance);
}
//如果有就直接使用
return instance;
}
}

    是不是也能实现单例所要求的功能呢?其实实现模式的方式有很多种,并不是只有模式的参考实现所实现

 

的方式,上面这种也能实现单例所要求的功能,只不过实现比较麻烦,不是太好而已,但在后面扩展单例模式

的时候会有用。

另外,模式是经验的积累,模式的参考实现并不一定是最优的,对于单例模式,后面会给大家一些更好的

实现方式。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics