java单例双重锁的线程安全问题,附解决方案
想必大家都写过单例模式,双重锁几乎是大家用到最多的写法如下所示。
public class Singleton {
private static Hello instance;
private Singleton() {}
public static Hello getInstance(){
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Hello();
}
}
}
return instance;
}
}
上面这段代码中instance = new Hello();这句话在单线程中是没有问题的,然而在多线程中是有安全问题的。
因为这涉及jvm指令重排序的问题,例如这段instance = new Hello();在内存中是如何执行的:
一种可能是:
a. 分配一块内存空间
b. 在内存中初始化一个Hello实例
c. 将声明的instance引用指向这个内存
另一种可能是:
a. 分配一块内存空间
b. 将声明的instance引用指向这个内存
c. 在内存中初始化一个Hello实例
针对下面这种可能,假设有两个线程A和B,A线程刚好执行到instance = new Hello();此时instance指向了一块内存空间,但此时该内存还没有进行实例化,B线程执行if (instance == null) 操作,此时instance已经指向了内存空间,instance已经不为null了,B线程就直接返回了instance实例,这时就出现问题了,因为B线程拿到了一个不完成的instance实例,这就是导致线程不安全的原因。
解决这个问题我总结了两个方案:
方案一
在成员变量加上volatile字段,其他代码不变,这就能解决指令重排的问题。
例如
private volatile static Hello instance;
方案二 使用内部静态类实现getInstance方法
public class Singleton {
public static Hello getInstance(){
return InnerSingleton.instance;
}
private static class InnerSingleton{
private static Hello instance = new Hello();
}
}
内部类InnerSingleton只有在getInstance第一次调用的时候才会被加载,实现了延迟加载的效果,而且加载过程是线程安全的实现了线程安全问题,内部类加载的时候只会实例化一次instance。
总结
个人建议使用内部类实现单例模式,希望对使用单例的小伙伴们有所帮助。