问题背景
单例模式是日常工作中很常见的一种设计模式,但是越简单的往往坑越多,梳理一波,查漏补缺。
一、什么是单例模式?
单例模式就是确保项目里某个类最多只能有一个实例,而且向整个系统提供这个实例。
二、单例模式有哪几种?
(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出不止一个这个“单例类”的对象,即破坏了这个单例模式。