单例模式的几种实现方式

369 阅读4分钟

实现单例模式,必须满足的条件:

  1. 单例模式的类必须为final(不允许继承)
  2. 私有化构造器(private)
  3. 建立获取实例对象的类方法(static)
//final不允许继承
pubilc final class Singleton{
  //私有构造函数,不允许外部new
	private Singleton(){}
	
  //获取实例对象的类方法
	public static Singleton getInstance(){
 		
    return instance;
	}  
}

各个实现方式优劣的判断维度:

  • 线程安全(保证单例)
  • 高性能(允许多线程同时获取实例对象)
  • 懒加载(要使用时才去实例化)

一、饿汉式

设置一个类变量(使用static修饰),在类中使用new方法直接返回一个对象。这样做的好处就是,可以保证在类加载阶段(类初始化)时就能完成instance实例的创建。因为作为类变量,可以在类初始化执行()方法中被赋予正确的值,()方法是在编译阶段生成的,已经包含在class文件中了,包含了所有类变量的赋值动作和静态语句块的执行代码,JVM会保证()方法在多线程环境下的同步语义。

但是饿汉式只能单纯保证多线程下的唯一实例,且是在类初始化阶段就生产了类对象,而不是需要使用到的时候再生成对象,也就是说不能进行懒加载,这样的不好的地方是如果这类中的成员都是比较重的资源的话,会比较占用多的内存资源。

private static Singleton instance = new Singleton();

二、懒汉式

要做到懒汉式,也就是懒加载,就是需要用到实例的时候才进行初始化,也就是说在类加载阶段不进行初始化。可以一开始设置类变量instance为null,后续在getInstance方法中进行判断,instance是否已经被实例化,若没有则new一个对象返回。

private static Singleton instance = null;

public static Singleton getInstance(){
  
  if(null == instance){
    
    return new Singleton();
  }
  
  return instance;
}

这样写在多线程环境下不能保证单例,因为getInstance方法中没有做同步处理,可能会导致instance被实例化多次。

这种就其实就是没有达到单例模式的基本要求,错误的写法。

三、懒汉式+同步方法

此时需要在getInstance方法中加上synchronized关键字进行同步控制

public static synchronized Singleton getInstance(){
  
  if(null == instance){
    return new Singleton();
  }
  
  return instance;
}

但是这样做会导致后续线程获取instance时性能低下,因为同一时刻只允许一个线程访问getInstance方法。(没必要为了保证第一次创建时的线程安全问题,而放弃后续使用性能)

四、Double-Check

if(null == instance){
  
  synchronized(Singleton.class){
    
    if(null == instance){
      return new Singleton();
    }
  }
}

即满足了懒加载,又满足了instance实例的唯一性,同时还可以允许多个线程同事访问getInstance方法。但是在多线程环境下,上面这段逻辑可能会导致空指针异常(且每次都要先进行判断,相当于多增加了一个判断,不过这种应该可以忽略不计),因为如果Singleton构造器中还需要初始化其他资源,由于JVM运行时指令重排序和Happens-Before规则,其他资源和instance实例顺序没有前后约束,则会出现instance已经实例化完成,而其他资源并没有完成实例化,另外一个线程访问进来后,判断instance不为null,直接使用类的实例变量资源时,会报空指针异常。

五、Volatile+Double-Check

public volatile static Singleton instance = null;  

通过加上volatile关键字,直接禁止JVM的指令重排序。

六、Holder

通过在类中建立静态内部类的方式持有实例对象。

private static class Holder{
  private static Singleton instance = new Singleton();
}

public static Singleton getInstance(){
  return Holder.instance;
}

把new Singleton()放在静态内部类中,在首次使用Holder类时,类加载初始化阶段执行的()方法,保证了同步语义(保证内存可见性,JVM指令的顺序性和原子性)。这种方式是最好的单例设计之一。

七、Enum

枚举类型不允许被继承,同样是线程安全的且只能被实例化一次。

public enum Singleton{
  
  INSTANCE;
  Singleton(){}
  
  public static void method(){
    
  }
  
  public static Singleton getInstance(){
    
    return INSTANCE;
  }
  
}

上面的代码不能实现懒加载,可以按照类似于Holder的方式进行改进:

public class Singleton{
  
  private Singleton(){}
  
  private enum EnumHolder{
    
    INSTANCE;
    private Singleton instance;
    
    EnumHolder(){
      this.instance = new Singleton();
    }
    
    private Singleton getSingleton(){
      return instance;
    }
  }
  
  public static Singleton getInstance(){
    
    return EnumHolder.INSTANCE.getSingleton();
  }
}

本文是出自《Java高并发编程详解》,记录下来供大家学习~