单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,一个类只有一个实例,即一个类只会存在一个对象实例。单例模式的实现需要注意以下两点:
- 类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
- 将该类的构造方法定义为私有方法,这样就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
在介绍单例模式的实现方式之前,需要强调一点:在多线程场景下,单例模式可能会出现线程安全问题,带来的后果就是会创建出多个对象实例,从而违反了单例模式中实例唯一的原则,针对这个问题通常使用锁来解决。
饿汉式(静态变量)
public class Singleton {
private static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
-
优点:静态变量在类加载阶段就被创建好了,根据jvm的类加载机制可知,类加载阶段线程安全,避免了线程安全问题
-
缺点:类加载阶段就完成了实例创建,如果该对象实例一直未被使用,也是一种内存浪费
懒汉式(线程不安全)
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
- 优点:是一种懒加载机制,开始时并未创建出对象实例,而是在使用时才创建出对象实例
- 缺点:在多线程场景下,一旦多个线程执行到
if (INSTANCE == null),并进行了线程上下文切换,此时就可能会创建出多个对象实例
懒汉式(通过synchronized保证线程安全)
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
- 优点:获取实例的方法进行加锁,确保了
INSTANCE只会被创建一份 - 缺点:针对获取实例的方法加锁,锁粒度太大,因为一旦
INSTANCE!=null,其实也就没这些问题,考虑将锁拿到方法内部,进行以下改造:
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
这种方式其实还是没有解决线程安全的问题,无非就是提高了方法的执行效率。当下的问题就是解决前面所提到的【一旦多个线程执行到if (INSTANCE == null),并进行了线程上下文切换,此时就可能会创建出多个对象实例】。
简而言之,目前的问题是:当一个线程进行上下文切换时,只感知到了上下文切换前INSTANCE的状态,而上下文切换后并没有去校验INSTANCE的状态。由此引出DCL双锁检测
懒汉式(DCL双锁检测)
public class Singleton {
private volatile static Singleton singleton;
public static Singleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (singleton == null) {
//竞争锁 原子操作
synchronized (Singleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
DCL双锁检测这种实现方式,有以下几点变化:
- 同步代码块内再次进行了一次判空校验(也就是线上上下文切换前后都校验了
- 变量singleton使用了volatile进行修饰(下篇文章好好聊下为啥使用了volatile,并不像八股中说的那么浅)
综合可见,这种实现方式的优点:线程安全、延迟加载、效率较高。
懒汉式(静态内部类)
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
静态内部类的优点是:外部类加载时并不会立即加载内部类,内部类不被加载则不去创建INSTANCE。即当Singleton第一次被加载时,并不会去加载SingletonInstance,只有当getInstance()方法第一次被调用时,需要去访问内部类中的属性时,才会触发内部类的加载动作,去创建INSTANCE,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化
除了上面总结的实现方式外,还有枚举这种天然单例的实现方式,但是我没咋用过,所以就不班门弄斧了。