Fork me on GitHub

单例模式

所有单例模式都有一个共性,那就是这个类没有自己的状态。也就是说无论这个类有多少个实例,都是一样的;然后除此者外更重要的是,这个类如果有两个或两个以上的实例的话程序会产生错误。

基于上述原因,非线程安全的实现方式,在此不再讨论。下面讨论的都是线程安全的一些实现方式和存在的问题。

双重加锁模式

public class Singleton{
  private volatile static Singleton singleton;
  private Singleton(){

  }

  public static Singleton getInstance(){
    if(null == singleton){
      synchronized(Singleton.class){
        if(null == singleton){
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }
}

双重加锁模式相对于普通的单例和加锁模式而言,从性能和线程安全上来说都有很大的提升和保障。然而双重加锁模式也存在一些隐蔽不易被发现的问题。首先我们要明白在JVM创建新的对象时,主要要经过三个步骤。

    • 分配内存
    • 初始化构造器
    • 将对象指向分配的内存地址

这样的顺序在双重加锁模式下是么有问题的,对象在初始化完成之后再把内存地址指向对象。但是现代的JVM会针对字节码进行调优,这样的话就有可能导致2和3的顺序是相反的,一旦出现这样的情况问题就来了。

所以更加理想的方案是利用静态内部类的方式来创建,因为静态属性由JVM确保第一次初始化时创建,因此也不用担心并发的问题出现。当初始化进行到一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以singleton仍然是单例的。

静态内部类的方式

public class Singleton{

  private Singleton(){}

  public static Singleton getInstance(){
    return InnerClassSingleton.singleton;
  }

  private class InnerClassSingleton{
    protected static Singleton singleton = new Singleton();
  }
}

然而,虽然静态内部类模式可以很好地避免并发创建出多个实例的问题,但这种方式仍然有其存在的隐患。

    • 一旦一个实例被持久化后重新生成的实例仍然有可能是不唯一的。
    • 由于java提供了反射机制,通过反射机制仍然有可能生成多个实例。

单例最优方案,枚举的方式

枚举实现单例的优势

  • 自由序列化;
  • 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
  • 线程安全;
public enum Singleton {
    INSTANCE;

    private Singleton(){}
}

参考文档:

posted @ 2016-12-10 10:12  秋楓  阅读(159)  评论(0编辑  收藏  举报