单例模式

    单例模式也许是最常见的一种设计模式了。看起来简单,实际上如果不注意细节,产生的问题,相对还是有些复杂的。

    在软件系统中,我们希望某些类始终保持最多一个实例对象的存在,来保证一致性或者节约内存等。这时,就用到了单例模式。
    单例模式的做法是,将类的构造方法私有化,不对外开放。在类的内部定义一个该类的静态实例成员。通过一个静态方法对外提供该成员。
    但这里就有一些问题了,这个实例化对象什么时候创建?
    通常有两种方法:饿汉式/懒汉式
  • 饿汉式:在定义时创建   instance = new A();
  • 懒汉式:在getInstance时,判断,如果没有实例化,先实例化再返回。称为延迟加载。
    • if( instance == null ) { instance = new A(); } return instance;
    这两种做法都有些问题。
  • 饿汉式在类初始化时就创建了instance实例,无法做到延迟加载。无论后面是否使用,都占用着内存。
  • 懒汉模式在多线程场景下,会创建多个实例,或者因为重排序在没有实例化完成时,却返回了该对象的引用,导致使用时出错。
    后来,人们发明了双重锁检查,但这任然是一个有错误的误人子弟的方式,代码如下:
private static Something instance = null;
public Something getInstance() {
  if (instance == null) {
    synchronized (this) {
      if (instance == null)
        instance = new Something();
    }
  }
  return instance;
}
    这段代码还是会因为重排序,导致new的对象没有初始化完成,却已经将引用赋给了instance。其他线程拿到一个没有初始化完成的实例,导致使用出错。
在1.5的jvm后,volatile关键字可以解决这个问题。如果将上述代码中的instance用volatile修饰,由于volatile变量的读写操作间存在happens-before关系,因此可以阻止重排序,使其他线程拿到的一定是初始化完成的实例。但是,volatile在拥有如此强大的功能的同时,他的性能开销也有很大程度的上升,已经快达到了同步的级别,所以这种方式虽然没有逻辑上的错误,但仍不是一个最近好的选择。
    人类是聪明的动物,总能想到解决为的办法。IODH技术,很好的解决了上面说的所有问题。代码如下:
 
public class IODHCode {
private static class Gener{
    private static IODHCode instance = new IODHCode();
}
public static IODHCode getInstance() {
    return Gener.instance;
}
}
    这种技术利用了内部静态类,内部静态类在调用它的时候才进行加载,并且由jvm保证其线程安全性。这样既做到了延迟加载,又在代码层面没有加锁,不影响性能。
 
    优点:
  • 提供对唯一实例的控制
  • 节约资源
    缺点:
  • 职责过重,既当工厂,又当产品
  • 长时间不用,会被垃圾回收,导致共享状态丢失。(有争议)
posted @ 2016-01-26 09:30  进击的璐璐  阅读(124)  评论(0编辑  收藏  举报