美文网首页
线程安全的延迟初始化:线程安全的创建单例

线程安全的延迟初始化:线程安全的创建单例

作者: 天冷请穿衣 | 来源:发表于2019-12-31 17:40 被阅读0次

    2019-12-31

    为什么要进行字段的延迟初始化?

    因为对字段的延迟初始化可以降低初始化类或创建实例的开销。

    不安全的双重检查加锁延迟初始化方案

    public class DoubleCheckd  {
        private DoubleCheckd  instance;
    
        public DoubleCheckd  getInstance(){
            if(instance == null){//1
                synchronized(DoubleCheckd .class){//2
                    if(instance == null){//3
                        instance = new DoubleCheckd();//4
                    }
                }
            }
            return instance;//5
        }
    }
    

    这个方案是线程不安全的,问题是由于代码行4引起的。因为4实际的动作会分成下面的三个步骤:
    a.在堆内存中开辟所需要的空间;
    b.初始化对象;
    c.将分配的空间地址赋值给引用变量。
    由于为了提高代码的执行性能,编译器和系统都会进行重排序,因此上述这三个步骤不一定是按照上面的顺序执行,b和c可能被重排序,即b先于c被执行。
    在单线程的情况下并不会出现任何问题的,但在多线程的情况就可能会有问题,因为当线程A刚完成步骤c但未执行b时,若恰好有线程B运行到1处,那么此时线程B就会获取到一个不为null但却未被初始化的实例对象,其后续的操作将发生不可预知的错误。
    知道了造成问题的原因解决方法也就出来了。因此只有解决如下两个问题即可:
    1)避免b和c的重排序
    2)使c操作不对外可见

    安全的延迟初始化方式

    1. 基于volatile的双重检查加锁方式
    public class DoubleCheckd  {
        private volatile DoubleCheckd  instance;
        public DoubleCheckd  getInstance(){
            if(instance == null){//1
                synchronized(DoubleCheckd .class){//2
                    if(instance == null){//3
                        instance = new DoubleCheckd();//4
                    }
                }
            }
            return instance;//5
        }
    }
    

    此优化方法很简单,就是在原来的基础上加上volatile修饰instance变量,因为根据JMM的规则,volatile能够禁止b、c的重排序。

    ps:本来曾想过使用initFlag==true代替instance==null,在代码4下面加上initFlag=true以避免b和c的重排序造成的问题,但实际上也是没用的,因为initFlag=true也可能会重排序到4前面。还是会存在使用未初始化完成的对象的危险。

    2. 基于类初始化的延迟方式
    public class ClassInitialization {
        private static class InitialInstanceClass{
            private static ClassInitialization instance = new ClassInitialization();//1
        }
    
        public static ClassInitialization getInstance(){
            return InitialInstanceClass.instance;//2
        }
    }
    

    该方式是利用多线程同时加载一个类时,jvm已保障了只有一个线程能够对该类进行初始化,从而保证了初始化的安全性。也即在多线程情况下,有且只有一个线程能够执行代码1,因此即使代码执行时出现重排序也不会被其它线程看见。

    若要对实例字段延迟初始化可以采用方式1,若要对静态字段延迟初始化可以采用方式2。

    相关文章

      网友评论

          本文标题:线程安全的延迟初始化:线程安全的创建单例

          本文链接:https://www.haomeiwen.com/subject/dwzboctx.html