采用延迟初始化来下降初始化类和建立对象的开销

在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类的初始化
        }  
      
    }
相关文章
相关标签/搜索