双重检测单例模式中使用volatile的原因

以前一直没在意双重检测单例模式中volatile的作用,最近又注意到了它的细节处的作用,在这里记录下。虽然现在单例模式的最佳选择是使用枚举,但通过这个增长知识也是不错的。下面是一般的双重检测单例模式的代码:

public class Single {
    private static volatile Single instance = null;

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

看起来好像没有必要使用volatile保证instance的可见性,因为  instance = new Single (); 这行是在synchronized里面的。但是这里的volatile并不是为了保证可见性的,而是为了防止指令重排造成返回的  instance 不正确的情况发生。

看line 5代码: instance = new Single ();对象的创建实际包含一下几步:

    1. 类是否已经加载,没加载就加载类 ; 2. 申请内存; 3 初始化内存(置0,null等等);4. 执行构造方法,初始化对象; 5. 将生成的对象赋给引用

问题就在于这几步指令是可能重排的,比如1 2 3 4 5,变成1 2 3 5 4。也就是说还没有执行构造方法将这个对象的属性初始化,各个属性都是默认值,就将这个对象赋给了引用instance了。假设有个线程A发生了上面说的情况,生成对象时执行了1 2 3 5步,这时候有个线程B执行line2发现instance不为null了,于是直接执行line6将还未执行构造方法的对象返回。因此为了万无一失,还是要使用volatile的,防止生成对象时第4步和第5步顺序颠倒。

posted on 2020-02-07 18:26  千山万水楼外楼  阅读(1004)  评论(1编辑  收藏  举报

导航