在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。
CASE:
public class Singleton{
private static Singleton instance = null;
private Singleton(){ // 将构造函数私有化
}
public static Singleton getInstance(){
if(instance == null){ // 第一次检查
synchronized (Singleton.class) { // 加锁
if (instance == null) { // 第二次检查
instance = new Singleton(); // 初始化instance对象
}
}
}
return instance;
}
}
分析:
1)线程A执行instance = new Singleton();这行代码可以分解为如下的3个步骤:
①类的加载、连接(验证->准备->解析)。
②初始化对象。 注:初始化后,类的加载就完成了。
③将instance指向刚分配的内存地址。 注:这一步和类的加载过程没有任何关系。
2)其中的②和③可能会被重排序:
分配对象的内存空间 --> 将instance指向刚分配的内存地址。(注意,此时对象还没有被初始化!) --> 初始化对象。
3)如果发生重排序,另一个并发执行的线程B就有可能在第一次检查时判断instance不为null,线程B接下来将访问instance所引用的对象,但此时这个对象可能还没有被A线程初始化!
实现线程安全的延迟初始化的2个方法:
1)基于volatile的解决方案,不允许②和③重排序。
原理:当声明对象的引用为volatile后,②和③之间的重排序,在多线程环境中将会被禁止。
优点:静态字段、实例字段均可以实现延迟初始化。
public class Singleton{
private volatile static Singleton instance = null;
private Singleton(){ // 将构造函数私有化
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if (instance == null) {
// instance是volatile变量,故instance初始化的时候没有进行重排序。
instance = new Singleton();
}
}
}
return instance;
}
}
2)基于类初始化的解决方案(使用静态内置类),允许②和③重排序,但不允许其它线程“看到”这个重排序。
原理:
[1]类在初始化时,必须先获取到Class对象的初始化锁,如果线程A已经获取到了Class对象的初始化锁,此时,其它线程因获取不到初始化锁,从而无法对类进行初始化操作。
[2]类初始化的时机:
1>使用new关键字来实例化对象的时候
2>读取或设置类的静态字段(注:被final修饰、已在编译期间把结果放入常量池的静态字段除外)。
3>以及调用类的静态方法时。
public class Singleton{
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance; // 触发SingletonHandler类的初始化
}
}
采用延迟初始化来降低初始化类和创建对象的开销
猜你喜欢
转载自blog.csdn.net/A__17/article/details/78187506
今日推荐
周排行