《JAVA与模式》之单例模式

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

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

  

  Lazy initialization holder class模式

  这个模式综合使用了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从根本上提供保障,绝对防止屡次实例化,是更简洁、高效、安全的实现单例的方式。

相关文章
相关标签/搜索