java设计模式之 单例 模式

       作为对象的创建模式,单例模式确保某一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类。为了实现这一功能,必须  隐藏  类的构造函数,即把构造函数声明为 private,并提供一个创建对象的方法,由于构造对象被声明为private,外界无法直接创建这个类型的对象,只能通过该类提供的方法来获取类的对象,要达到这样的目的只能把创建对象的方法声明为static。

一。 单例模式的 优点

1.1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显;

1.2. 由于单例模式只生成一个实例,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如 读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留的方式来解决(在 java EE 中采用单例模式时需要注意 JVM 垃圾回收机制);

1.3. 单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作;

1.4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,如 可以设计一个单例类,负责所有数据表的映射处理;

二。单例模式的 缺点

2.1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本没有第二种途径。(因为单例模式要求 自行实例化,并且提供单一实例,接口或抽象类是不可能被实例化的,所以单例模式不能增加接口;特殊情况下,单例模式可以实现接口、被继承等,需要在系统开发中根据环境判断);

2.2. 单例模式 对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock 的方式虚拟一个对象;

2.3. 单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把 “ 要单例 ”和业务逻辑融合在一个类中;

三。单例模式的 使用场景

       在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现 “ 不良反应”,可以采用单例模式,具体场景如下:

3.1. 要求生成 唯一*** 的环境;

3.2. 在整个项目中需要一个 共享访问点 或 共享数据如一个web页面上的计数器,可以不用把每次刷新都记录到数据库中,用单例模式保持计数器的值并确保是线程安全的;

3.3. 创建一个对象需要消耗的资源过多,如要访问 IO 和 数据库等资源;

3.4. 需要定义大量的静态常量和静态方法(如 工具类)的环境,可采用单例模式(当然,也可以直接声明为 static 的方式)

四。单例模式的 实现

4.1. 饿汉  模式(通用代码,建议使用)

java设计模式之 单例 模式

      注解:饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题饿汉就是类一旦加载,单例初始化就开始,保证在调用getInstance时,单例是已经存在的了

      缺点:当类装载的时候就会创建类的实例,不管你用不用,都会先创建出来,无法做到延迟创建对象,从而降低内存的使用率。

4.2. 懒汉  模式(线程不安全)

java设计模式之 单例 模式

    注解:懒汉模式中,只有当调用  getInstance()的时候,并且判断 instance 为 null 时才创建一个实例,构造函数私有,确保每次都只创建一个,避免重复创建。

    缺点:只在 单线程 的情况下正常运行,在多线程的情况下,就会出问题。例如: 一个线程 A 执行到  instance = new Singleton(); 但还没有获得对象(对象初始化是需要时间的),第二个线程 B 也在执行,执行到  if (instance == null) 判断,那么线程 B 获得的判断条件也是为 true ,于是继续运行下去,线程 A 获得了一个对象,线程 B 也获得了一个对象,在内存中就出现了两个对象。

4.3. 懒汉  模式(线程安全)——在 getInstance 方法  上加同步

java设计模式之 单例 模式

     注解:在方法调用上加了同步,使得在多线程的情况下可以用。例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。

     缺点:每次通过 getInstance( ) 得到 instance 实例的时候都有一个试图去获取同步锁的过程,可进入同步锁和释放锁的耗时可是很大的。单例模式中第一次创建实例后,后面都不需要同步了(反正都是同一个了),而每次都要同步,会影响性能。

4.4. 懒汉  模式(线程安全,可行)—— 双重校验锁

     “双重检查加锁”机制,指的是:并不是每次进入 getInstance()都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,避免了每次都同步的性能损耗。

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

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

java设计模式之 单例 模式

     注解:只有当instance为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。

     缺点:用双重if判断,复杂,容易出错。

4.5. 懒汉  模式(线程安全,建议使用)—— 静态内部类

    采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。

java设计模式之 单例 模式

      注解: 把Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的当getInstance方法第一次被调用的时候,它第一次读取 LazyHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。

     优点:达到了lazy loading的效果(按需创建实例);getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本,又保证了性能。