单例模式下的懒加载

单例模式下的懒加载

Sardinary 🐱🍭🐱

在单例模式下,我们会将单例类的构造方法私有,并在类中唯一实例化一个对象。

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static final Singleton INSTANCE = new Singleton();

public static Singleton getInstance() {
return INSTANCE;
}

private Singleton() {
}
}

此时会马上创建单例对象。

❓ 有的时候可能实例话非常占用资源,我们想延时加载/懒加载单例对象,应该如何实现?

很容易我们会想到,可以先不实例化单例对象,等调用getInstance方法放入时候再实例对象。

1
2
3
4
5
6
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}

在单线程模式下,可以实现懒加载。但是在多线程模式下,就无法保证单例性。

在多线程下最容易想到的解决方式就是直接加锁,但是加锁会严重损失性能,所以并不推荐这种方式。

为了解决性能损失问题,我们可以采用双检加锁策略。

1
2
3
4
5
6
7
8
9
10
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}

这段代码看起比较完美,但是事实上可能出现一些致命错误。

假设A线程先进入同步块,此时instance为空。由于JVM的优化机制,可能会先为instance分配一块内存空间,但是还未初始化的时候,B线程进入第一个if判断,发现instance不为空,于是直接使用instance。但是此时instance可能还未初始化完成,就会出现错误。

❓ 那么如何实现单例对象的懒加载的同时还能保证线程安全?

  • 利用静态内部类/ClassLoader机制

    JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。利用classloder的机制来保证初始化instance时只有一个线程。

    Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance,实现懒加载。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
    private static Singleton instance = new Singleton();
    }
    public static Singleton getInstance() {
    return SingletonHolder.instance;
    }
    }

  • 利用volatile

    volatile可以保证有序性和可见性,配合双检加锁,从而保证线程安全

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Singleton {
    private volatile static Singleton singleton;

    private Singleton() {}

    public static Singleton getSingleton() {
    if (singleton == null) {
    synchronized (Singleton.class) {
    if (singleton == null) {
    singleton = new Singleton();
    }
    }
    }
    return singleton;
    }
    }

    但是volatile屏蔽了一些执行优化,例如指令重排等,所以会降低一些运行效率。

  • 使用枚举类

    1
    2
    3
    4
    5
    6
    7
    public enum Singleton {
    INSTANCE;

    public void whateverMethod() {
    }
    }

    不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

目录
单例模式下的懒加载