以前一直没在意双重检测单例模式中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步顺序颠倒。