问题背景 单例模式是日常工作中很常见的一种设计模式,但是越简单的往往坑越多,梳理一波,查漏补缺。 一、什么是单例模式? 单例模式就是确保项目里某个类最多只能有一个实例,而且向整个系统提供这个实例。 二、单例模式有哪几种? (1)饿汉式单例模式 public class SingleDemo { // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){
}
// 准备一个类属性,指向一个实例化对象。因为是类属性,所以只有一个
private static SingleDemo instance = new SingleDemo();
//public static 方法,提供给调用者获取定义的对象
public static SingleDemo getInstance(){
return instance;
}
} 注意:在类加载时,已经自行实例化,可能需要考虑内存开销问题。 (2)懒汉式单例模式 提到懒汉式单例模式,很多同学容易写成以下这种: // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){ }
private static SingleDemo instance;
public static SingleDemo getInstanceA() {
if (null == instance) {
instance = new SingleDemo();
}
return instance;
} 此方式在单线程的时候工作正常,但在多线程的情况下就有问题了。如果两个线程同时运行到判断instance是否为null的if语句,并且instance的确没有被创建时,那么两个线程都会创建一个实例,此时就不再满足单例模式的要求了。 进一步优化,直接在getInstanceA()方法加上sychronized关键字,得到2.0版本,如下所示: // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){ }
private static SingleDemo instance;
public synchronized static SingleDemo getInstanceA() {
if (null == instance) {
instance = new SingleDemo();
}
return instance;
}
但每次调用getInstanceB()方法时都被synchronized关键字锁住了,会引起线程阻塞,影响程序的性能。然后加以改进,不让线程每次调用getInstanceC()方法时都加锁,而只是在实例未被创建时再加锁,得到3.0版本,如下所示: // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){ }
private static SingleDemo instance;
public static SingleDemo getInstance() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) {
synchronized (SingleDemo.class) {
instance = new SingleDemo();
}
}
return instance;
}
这样相比直接在方法上加synchronized关键字的2.0版本,确实效率提高了,不过这个还有个问题,如果有多个线程进入到 if (instance == null) 判断后,再竞争锁,可能就会产生多个实例,单例模式就失效了,也不符合。那么最后就进化到了版本4.0,也就是双重检验锁版本,如下所示: // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){ }
private static SingleDemo instance;
public static SingleDemo getInstance() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) {
synchronized (SingleDemo.class) {
if (instance == null) {
instance = new SingleDemo();
}
}
}
return instance;
}
(3)静态内部类方式(推荐) 加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。 ——饿汉式对应的内存优化 由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。 实例如下: // 私有化构造方法使得该类无法在外部通过new进行实例化 private SingleDemo(){ }
private static final class SingleDemoHolder {
private static final SingleDemo instance = new SingleDemo();
}
public static SingleDemo getInstance() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
return SingleDemoHolder.instance;
}
(4)枚举方式(推荐) enum Single { SINGLE;
private Single() {
}
public void print() {
System.out.println("hello world");
}
} 创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。保证只有一个实例 三、怎么破坏单例模式? 私有化方法可以通过反射调用,这样就可以new出不止一个这个“单例类”的对象,即破坏了这个单例模式。