设计模式(4) -- 单例模式(Singleton)

设计模式(4)  -- 单例模式(Singleton)

试想一个读取配置文件的需求,创建完读取类后通过New一个类的实例来读取配置文件的内容,在系统运行期间,系统中会存在很多个该类的实例对象,也就是说系统中会同时存在多份配置文件的内容,这样会严重浪费内存资源。这样需要实现:在一个系统运行期间,只要一个类实例就可以了。

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

现在一个类能够被创建多个实例,问题的根源在于类的构造方法是公开的,也就是可以让类的外部通过构造方法创建多个实例。换句话说,只要类的构造方法能让类的外部访问,就没有办法去控制外部来创建这个类的实例个数。

要想控制一个类只被创建一个实例,那么首要的问题就是要把创建实例的权限收回来,让类自身来负责自己类实例的创建工作,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方式。

在Java中,单例模式分为两种,懒汉式和饿汉式。

懒汉式:

public classSingletonLan {

privatestaticSingletonLan uniqueInstance = null;

privateSingletonLan(){

}

publicstaticsynchronizedSingletonLan getInstance(){

if(uniqueInstance==null){

uniqueInstancenew SingletonLan();

}

return uniqueInstance;

}

publicvoidsingletonOPeration(){

}

privateString singletonData;

publicStringgetSingletonData(){

return singletonData;

}

}

饿汉式:

public classSingletonE {

private static SingletonE uniqueInstance= newSingletonE();

privateSingletonE(){

}

publicstaticSingletonEgetInstance(){

return uniqueInstance;

}

publicvoidsingletonOPeration(){

}

privateString singletonData;

publicString getSingletonData(){

return singletonData;

}

}

Java里面实现的单例是一个虚拟机范围。因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。这就意味着如果一个虚拟机里面有很多个ClassLoader,而且这些ClassLoader都装载某个类的话,就算这个类是单例,它也会产生很多个实例。当然,如果一个机器上有多个虚拟机,那么每个虚拟机里面都应该至少有一个这个类的实例,也就是说整个机器上就有很多个实例,更不会是单例了。

单例模式的精粹是static变量在类装载的时候进行初始化;多个实例的static变量会共享同一块内存区域。

单例模式有一种应用叫延迟加载(Lazy Load):一开始不需要加载资源或者数据,一直等,等到马上要使用这个资源或者数据的时候才去加载。

利用缓存来实现单例模式:

import java.util.HashMap;

import java.util.Map;

public classSingletonCache {

private final static String DEFAULT_KEY="One";

private static Map<Object,SingletonCache> map = newHashMap<Object,SingletonCache>();

private SingletonCache(){

}

public static SingletonCache getInstance(){

SingletonCache instance = (SingletonCache)map.get(DEFAULT_KEY);

if(instance==null){

instance = new SingletonCache();

map.put(DEFAULT_KEY,instance);

}

return instance;

}

}

从线程安全上讲,懒汉式是线程不安全的,饿汉式是线程安全的(虚拟机保证只会加载一次,在装载类的时候是不会发生并发的),但是加了synchronized关键字的懒汉式是线程安全的,只是效率下降了。

可以使用双重检查加锁机制,指的是并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了。

双重检查加锁机制的实现用到关键字volatile,意思是被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。由于volatile关键字可能会屏蔽虚拟机中一些必要的代码优化,所以运行效率并不是很高。

public classSingletonVolatile {

privatevolatilestaticSingletonVolatile instance = null;

privateSingletonVolatile(){

}

publicstaticSingletonVolatilegetInstance(){

if(instance== null){

synchronized(SingletonVolatile.class){

if(instance==null){

instance = newSingletonVolatile();

}

}

}

return instance;

}

}

Lazyinitialization holder class模式综合实现了延迟加载和线程安全。在类加载的时候不去初始化对象。

public classSingletonClass {

privatestaticclassSingletonHolder{

private staticSingletonClass instance = newSingletonClass();

}

privateSingletonClass(){

}

publicstaticSingletonClassgetInstance(){

return SingletonHolder.instance;

}

}

据说单元素的枚举类型已经成为实现Singleton的最佳方法。

上一篇:每天一个设计模式-4 单例模式(Singleton)


下一篇:com.mysql.jdbc.PacketTooBigException: Packet for query is too large (1169 > 1024)