[干货]设计模式:单例模式

583 阅读3分钟

设计模式千千万,总是单例最常见。

单例模式的定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

六种单例的创建方式

1.饿汉式

public class Singleton {
  private static Singleton instance = new Singleton();
  private Singleton() {}
  public static Singleton getInstance() {
    return instance;
  }
}

优点: 基于类的加载机制,避免了多线程同步问题,加载速度快。

缺点: 在类加载的时候就完成初始化,没有懒加载,如果没有使用这个实例,会造成内存浪费。

2.懒汉式-线程不安全版

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

优点: 第一次调用是才初始化对象,避免浪费资源

缺点: 加载速度慢,线程不安全

3.懒汉式-线程安全版(synchronized加锁)

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

优点: 多线程中保证线程安全 缺点: 每次获取对象实例,都需要进行同步,造成不必要的同步开销。

4.双重校验锁

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

优点: 线程安全,懒加载,减少同步开销

缺点: 第一个获取对象速度稍慢,但其在某些情况下也会出现失效的情况,并不是完美的方式。

这里面使用了两次判空:第一次为了不必要的加锁同步,第二次是确保在instance为null的情况下才创建实例,避免多次创建。

方法中还是用了关键字volatile对变量进行修饰,有如下几个作用:

1.在Java内存模型中volatile可以保证可见性,及防止程序指令重排序。

2.对象的创建分为如下几个步骤:

instance = new Singleton();
  • 1.为instance分配内存空间
  • 2.初始化instance
  • 3.将instance指向内存地址

如果不加volatile的话,程序的执行顺序就可能变成1->3->2,多线程中就会导致线程获取一个没有初始化的实例。例如线程a 执行了1,3, 此时线程b调用getInstance()发现instance不为空,返回instance,但此时instance还未初始化。

5.静态内部类

public class Singleton {
  private Singleton() {}
  public static Singleton getInstance() {
    return SingletonHolder.sInstance;
  } 
  private static class SingletonHolder {
    private static final Singleton sInstance = new Singleton();
  }
}

第一次加载类的时候不会初始化instance,只有第一次调用getInstance()的时候才会进行加载SingleHolder并初始化instance,保证线程安全,也能保证实例唯一,推荐使用这种方式。

6.枚举

public enum Singleton {
INSTANCE;
public void doSomeThing(){}
}

默认枚举单例的创建是线程安全的,并且任何情况下都是单例。

以上就是6中常见的单例创建形式,按需使用吧。

单例的使用场景

  • 整个项目需要一个共享访问点或者数据
  • 创建一个对象需要耗费的资源太多,比如访问数据库资源等
  • 工具类对象