在阎宏博士的《JAVA与模式》一书中开头是这样描述单例模式的:java
做为对象的建立模式,单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。这个类称为单例类。缓存
单例模式的特色:安全
public class EagerSingleton { private static EagerSingleton instance = new EagerSingleton(); /** * 私有默认构造子 */ private EagerSingleton(){} /** * 静态工厂方法 */ public static EagerSingleton getInstance(){ return instance; } }
上面的例子中,在这个类被加载时,静态变量instance会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被建立出来了。多线程
饿汉式实际上是一种比较形象的称谓。既然饿,那么在建立对象实例的时候就比较着急,饿了嘛,因而在装载类的时候就建立对象实例。并发
private static EagerSingleton instance = new EagerSingleton();
饿汉式是典型的空间换时间,当类装载的时候就会建立类的实例,无论你用不用,先建立出来,而后每次调用的时候,就不须要再判断,节省了运行时间。性能
public class LazySingleton { private static LazySingleton instance = null; /** * 私有默认构造子 */ private LazySingleton(){} /** * 静态工厂方法 */ public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
上面的懒汉式单例类实现里对静态工厂方法使用了同步化,以处理多线程环境。
懒汉式实际上是一种比较形象的称谓。既然懒,那么在建立对象实例的时候就不着急。会一直等到立刻要使用对象实例的时候才会建立,懒人嘛,老是推脱不开的时候才会真正去执行工做,所以在装载对象的时候不建立对象实例。优化
private static LazySingleton instance = null;
懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否须要建立实例,浪费判断的时间。固然,若是一直没有人使用的话,那就不会建立实例,则节约内存空间spa
因为懒汉式的实现是线程安全的,这样会下降整个访问的速度,并且每次都要判断。那么有没有更好的方式实现呢?线程
可使用“双重检查加锁”的方式来实现,就能够既实现线程安全,又可以使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?code
所谓“双重检查加锁”机制,指的是:并非每次进入getInstance方法都须要同步,而是先不一样步,进入方法后,先检查实例是否存在,若是不存在才进行下面的同步块,这是第一重检查,进入同步块事后,再次检查实例是否存在,若是不存在,就在同步的状况下建立一个实例,这是第二重检查。这样一来,就只须要同步一次了,从而减小了屡次在同步状况下进行判断所浪费的时间。
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,全部对该变量的读写都是直接操做共享内存,从而确保多个线程能正确的处理该变量。
注意:在java1.4及之前版本中,不少JVM对于volatile关键字的实现的问题,会致使“双重检查加锁”的失败,所以“双重检查加锁”机制只只能用在java5及以上的版本。
public class Singleton { private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //先检查实例是否存在,若是不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的建立实例 synchronized (Singleton.class) { //再次检查实例是否存在,若是不存在才真正的建立实例 if(instance == null){ instance = new Singleton(); } } } return instance; } }
这种实现方式既能够实现线程安全地建立实例,而又不会对性能形成太大的影响。它只是第一次建立实例的时候同步,之后就不须要同步了,从而加快了运行速度。
提示:因为volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,因此运行效率并非很高。所以通常建议,没有特别的须要,不要使用。也就是说,虽然可使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,能够根据状况来选用。
根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能实现线程安全呢?
这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。
1.相应的基础知识
简单点说,类级内部类指的是,有static修饰的成员式内部类。若是没有static修饰的成员式内部类被称为对象级内部类。
类级内部类至关于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,所以可直接建立。而对象级内部类的实例,是绑定在外部对象实例中的。
类级内部类中,能够定义静态的方法。在静态方法中只可以引用外部类中的静态成员方法或者成员变量。
类级内部类至关于其外部类的成员,只有在第一次被使用的时候才被会装载。
你们都知道,在多线程开发中,为了解决并发问题,主要是经过使用synchronized来加互斥锁进行同步控制。可是在某些状况中,JVM已经隐含地为您执行了同步,这些状况下就不用本身再来进行同步控制了。这些状况包括:
1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
2.访问final字段时
3.在建立线程以前建立对象时
4.线程能够看见它将要处理的对象时
2.解决方案的思路
要想很简单地实现线程安全,能够采用静态初始化器的方式,它能够由JVM来保证线程的安全性。好比前面的饿汉式实现方式。可是这样一来,不是会浪费必定的空间吗?由于这种实现方式,会在类装载的时候就初始化对象,无论你需不须要。
若是如今有一种方法可以让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去建立对象实例。这样一来,只要不使用到这个类级内部类,那就不会建立对象实例,从而同时实现延迟加载和线程安全。
示例代码以下:
public class Singleton { private Singleton(){} /** * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 * 没有绑定关系,并且只有被调用到时才会装载,从而实现了延迟加载。 */ private static class SingletonHolder{ /** * 静态初始化器,由JVM来保证线程安全 */ private static Singleton instance = new Singleton(); } public static Singleton getInstance(){ return SingletonHolder.instance; } }
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,致使SingletonHolder类获得初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而建立Singleton的实例,因为是静态的域,所以只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优点在于,getInstance方法并无被同步,而且只是执行一个域的访问,所以延迟初始化并无增长任何访问成本。
按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现Singleton的最佳方法。用枚举来实现单例很是简单,只须要编写一个包含单个元素的枚举类型便可。
public enum Singleton { /** * 定义一个枚举的元素,它就表明了Singleton的一个实例。 */ uniqueInstance; /** * 单例能够有本身的操做 */ public void singletonOperation(){ //功能处理 } }
使用枚举来实现单实例控制会更加简洁,并且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止屡次实例化,是更简洁、高效、安全的实现单例的方式。