单例模式下的懒加载
在单例模式下,我们会将单例类的构造方法私有,并在类中唯一实例化一个对象。
1 | public class Singleton { |
此时会马上创建单例对象。
❓ 有的时候可能实例话非常占用资源,我们想延时加载/懒加载单例对象,应该如何实现?
很容易我们会想到,可以先不实例化单例对象,等调用getInstance方法放入时候再实例对象。
1 | public static Singleton getInstance() { |
在单线程模式下,可以实现懒加载。但是在多线程模式下,就无法保证单例性。
在多线程下最容易想到的解决方式就是直接加锁,但是加锁会严重损失性能,所以并不推荐这种方式。
为了解决性能损失问题,我们可以采用双检加锁策略。
1 | public static Singleton getInstance() { |
这段代码看起比较完美,但是事实上可能出现一些致命错误。
假设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
10public 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
16public 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
7public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。