在学习过之前的《单例》之后,相信大家一定对单例有了很深的理解,对于双重校验锁的单例实现大家一定都不陌生。
不知道大家有没有关注过一个细节,那就是在双重校验锁中的getInstance方法中,定义了一个局部变量来接收Singleton的单例对象。代码实现如下:
public class Singleton {
private static volatile Singleton instance=null;
private Singleton() {
}
public static Singleton getInstance() {
Singleton temp=instance; // 定义了一个局部变量
if (null == temp) {//对局部变量进行非空判断
synchronized (Singleton.class) {
temp = instance;
if (null == temp) {
temp=new Singleton(); //对局部变量进行赋值
instance=temp;//再将局部变量赋值给单例对象
}
}
}
return instance;//返回单例对象
}
}
以上,便是一个双重校验锁的代码,可以看到,在getInstance方法中定义了一个局部变量temp,在操作过程中都是对这个临时的局部变量进行的操作,最后再赋值给真正的单例对象的。
在很多源码中,也都有类似的做法,如Spring中有以下代码:
private static volatile ReactiveAdapterRegistry sharedInstance;
public static ReactiveAdapterRegistry getSharedInstance() {
ReactiveAdapterRegistry registry = sharedInstance;
if (registry == null) {
synchronized (ReactiveAdapterRegistry.class) {
registry = sharedInstance;
if (registry == null) {
registry = new ReactiveAdapterRegistry();
sharedInstance = registry;
}
}
}
return registry;
}
那么,你知道为什么要这么做吗?
这里其实和volatile有关,我们知道,双重校验锁单例为了避免发生指令重排,一定要使用volatile来定义单例对象。
其实如果大家对于volatile有深入理解的话,这个问题其实不难回答。为了保证共享变量在并发场景下的内存可见性,volatile变量的操作前后都会通过插入内存屏障来进行数据同步,即将线程的本地内存数据同步到主内存(或从主内存将数据同步到线程的本地内存)
而这个过程其实是有很大的损耗的,我们可以想办法降低对于volatile变量的访问次数,那就是通过定义局部变量的方式。
因为局部变量并不是共享的,所以不需要进行线程本地内存和主存之间的数据同步,操作效率就会很高。
所以,使用局部变量,是一种性能提升的方式,可以减少主存与线程内存的拷贝次数。