有些时候,容许自由建立某个类的实例是没有意义,还可能形成系统性能降低(由于建立对象所带来的系统开销问题)。例如整个Windows系统只有一个窗口管理器,只有一个回收站等。在Java EE应用中可能只须要一个数据库引擎访问点,Hibernate访问时只须要一个SessionFactory实例,若是在系统中为它们建立多个实例就没有太大的意义。java
若是一个类始终只能建立一个实例,则这个类被称为单例类,这种模式就被称为单例模式。shell
对Spring框架而言,能够在配置Bean实例时指定scope="singleton"类配置单例模式。不只如此,若是配置<bean .../>元素时没有指定scope属性,则该Bean实例默认是单例的行为方式。数据库
Spring推荐将全部业务逻辑组件、DAO组件、数据源组件等配置成单例的行为方式,由于这些组件无须保存任何用户状态,故全部客户端均可以共享这些业务逻辑组件、DAO组件,所以推荐奖这些组件配置成单例的行为方式。设计模式
在阎宏博士的《JAVA与模式》一书中开头是这样描述单例模式的:缓存
做为对象的建立模式,单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。安全
单例模式有如下几个特色:多线程
单例模式能够说是最简单的设计模式了,它仅有一个角色Singleton。框架
Singleton:单例。性能
public class EagerSingleton { private EagerSingleton() { } private static EagerSingleton instance = new EagerSingleton(); public static EagerSingleton getInstance() { return instance; } }
上面的例子中,在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被建立出来了。学习
饿汉式实际上是一种比较形象的称谓。既然饿,那么在建立对象实例的时候就比较着急,饿了嘛,因而在装载类的时候就建立对象实例。
饿汉式是典型的空间换时间,当类装载的时候就会建立类的实例,无论你用不用,先建立出来,而后每次调用的时候,就不须要再判断,节省了运行时间。
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; }
==================================================================================================
做者:欧阳鹏 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:http://blog.csdn.net/ouyang_peng
==================================================================================================