[置顶] 个人设计模式学习笔记------>单例模式(Singleton)

1、前言

有些时候,容许自由建立某个类的实例是没有意义,还可能形成系统性能降低(由于建立对象所带来的系统开销问题)。例如整个Windows系统只有一个窗口管理器,只有一个回收站等。在Java EE应用中可能只须要一个数据库引擎访问点,Hibernate访问时只须要一个SessionFactory实例,若是在系统中为它们建立多个实例就没有太大的意义。java

若是一个类始终只能建立一个实例,则这个类被称为单例类,这种模式就被称为单例模式。shell

对Spring框架而言,能够在配置Bean实例时指定scope="singleton"类配置单例模式。不只如此,若是配置<bean .../>元素时没有指定scope属性,则该Bean实例默认是单例的行为方式。数据库

Spring推荐将全部业务逻辑组件、DAO组件、数据源组件等配置成单例的行为方式,由于这些组件无须保存任何用户状态,故全部客户端均可以共享这些业务逻辑组件、DAO组件,所以推荐奖这些组件配置成单例的行为方式。设计模式

2、基本定义

在阎宏博士的《JAVA与模式》一书中开头是这样描述单例模式的:缓存

做为对象的建立模式,单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。安全

单例模式有如下几个特色:多线程

 

    1. 单例类只能有一个实例。
    2. 单例类必须本身建立本身的惟一实例。
    3. 单例类必须给全部其余对象提供这一实例。
单例模式的模式结构:

 单例模式能够说是最简单的设计模式了,它仅有一个角色Singleton。框架

 

 

         Singleton:单例。性能

 

 

、单例模式的具体实现

一、饿汉式实现单例模式

public class EagerSingleton {
	private EagerSingleton() {
	}

	private static EagerSingleton instance = new EagerSingleton();

	public static EagerSingleton getInstance() {
		return instance;
	}
}

上面的例子中,在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被建立出来了。学习

饿汉式实际上是一种比较形象的称谓。既然饿,那么在建立对象实例的时候就比较着急,饿了嘛,因而在装载类的时候就建立对象实例。

饿汉式是典型的空间换时间,当类装载的时候就会建立类的实例,无论你用不用,先建立出来,而后每次调用的时候,就不须要再判断,节省了运行时间。

2、懒汉式实现单例模式

public class LazySingleton {
	private LazySingleton() {
	}

	private static LazySingleton instance = null;

	public static synchronized LazySingleton getInstance() {
		if (instance==null) {
			instance=new LazySingleton();
		}
		return instance;
	}
}
上面的懒汉式单例类实现里对静态工厂方法使用了同步化,以处理多线程环境。
懒汉式实际上是一种比较形象的称谓。既然懒,那么在建立对象实例的时候就不着急。会一直等到立刻要使用对象实例的时候才会建立,懒人嘛,老是推脱不开的时候才会真正去执行工做,所以在装载对象的时候不建立对象实例。

懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否须要建立实例,浪费判断的时间。固然,若是一直没有人使用的话,那就不会建立实例,则节约内存空间。

因为懒汉式的实现是线程安全的,这样会下降整个访问的速度,并且每次都要判断。那么有没有更好的方式实现呢?

三、双重检查加锁实现单例模式

可使用“双重检查加锁”的方式来实现,就能够既实现线程安全,又可以使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?

所谓“双重检查加锁”机制,指的是:并非每次进入getInstance方法都须要同步,而是先不一样步,进入方法后,先检查实例是否存在,若是不存在才进行下面的同步块,这是第一重检查,进入同步块事后,再次检查实例是否存在,若是不存在,就在同步的状况下建立一个实例,这是第二重检查。这样一来,就只须要同步一次了,从而减小了屡次在同步状况下进行判断所浪费的时间。

“双重检查加锁”机制的实现会使用关键字volatile它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,全部对该变量的读写都是直接操做共享内存,从而确保多个线程能正确的处理该变量。

注意:在java1.4及之前版本中,不少JVM对于volatile关键字的实现的问题,会致使“双重检查加锁”的失败,所以“双重检查加锁”机制只只能用在java5及以上的版本。

public class DoubleCheckLockSingleton {
	private DoubleCheckLockSingleton() {
	}

	/**
	 * 关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,
	 * 全部对该变量的读写都是直接操做共享内存,从而确保多个线程能正确的处理该变量
	 */
	private static volatile DoubleCheckLockSingleton instance = null;

	public static DoubleCheckLockSingleton getInstance() {
		//先检查实例是否存在,若是不存在才进入下面的同步块
		if (instance == null) { 
			 //同步块,线程安全的建立实例
			synchronized (DoubleCheckLockSingleton.class) { 
				//再次检查实例是否存在,若是不存在才真正的建立实例
				if (instance == null) {
					instance = new DoubleCheckLockSingleton();
				}
			}
		}
		return instance;
	}
}

这种实现方式既能够实现线程安全地建立实例,而又不会对性能形成太大的影响。它只是第一次建立实例的时候同步,之后就不须要同步了,从而加快了运行速度。

因为volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,因此运行效率并非很高。所以通常建议,没有特别的须要,不要使用。也就是说,虽然可使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,能够根据状况来选用。

(更多关于双重检查锁定单例能够参考:个人Java开发学习之旅------>Java双重检查锁定及单例模式详解

根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能实现线程安全呢?

四、静态内部类实现单例模式

public class InnerClassSingleton {
	private InnerClassSingleton() {
	}

	/**
	 * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
	 *  并且只有被调用到时才会装载,从而实现了延迟加载。
	 */
	private static class InnerClassSingletonHolder {
		/**
		 * 静态初始化器,由JVM来保证线程安全
		 */
		private static InnerClassSingleton instance = new InnerClassSingleton();
	}

	public static InnerClassSingleton getInstance() {
		return InnerClassSingletonHolder.instance;
	}
}

当getInstance方法第一次被调用的时候,它第一次读取InnerClassSingletonHolder.instance,致使InnerClassSingletonHolder类获得初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而建立InnerClassSingleton的实例,因为是静态的域,所以只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

这个模式的优点在于,getInstance方法并无被同步,而且只是执行一个域的访问,所以延迟初始化并无增长任何访问成本。

五、枚举实现单例模式

public enum EmunSingleton {
	/**
          * 定义一个枚举的元素,它就表明了EmunSingleton的一个实例。
        */
	INSTANCE;
}
 
使用枚举来实现单实例控制会更加简洁,并且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止屡次实例化,
是更简洁、高效、安全的实现单例的方式。可是失去了类的一些特性,没有延迟加载。

4、单例模式的优缺点

 

一、优势

  • 提供了对惟一实例的受控访问。由于单例类封装了它的惟一实例,因此它能够严格控制客户怎样以及什么时候访问它,并为设计及开发团队提供了共享的概念。
  • 因为在系统内存中只存在一个对象,所以能够节约系统资源,对于一些须要频繁建立和销毁的对象,单例模式无疑能够提升系统的性能。
  • 容许可变数目的实例。咱们能够基于单例模式进行扩展,使用与单例控制类似的方法来得到指定个数的对象实例。

二、缺点

  • 因为单例模式中没有抽象层,所以单例类的扩展有很大的困难。
  • 单例类的职责太重,在必定程度上违背了“单一职责原则”。由于单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的建立和产品的自己的功能融合到一块儿。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库链接池对象设计为单例类,可能会致使共享链接池对象的程序过多而出现链接池溢出;如今不少面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,所以,若是实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将从新实例化,这将致使对象状态的丢失。

5、单例模式的使用场景

  • 系统只须要一个实例对象,如系统要求提供一个惟一的序列号生成器,或者须要考虑资源消耗太大而只容许建立一个对象。
  • 客户调用类的单个实例只容许使用一个公共访问点,除了该公共访问点,不能经过其余途径访问该实例。
  • 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,若是一个类能够有几个实例共存,就须要对单例模式进行改进,使之成为多例模式。

==================================================================================================

  做者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================

相关文章
相关标签/搜索