在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类的初始化 } }