一个问题:双重检查加锁为什么用了 volatile 就可以正确工作?
反过来问:不用 volatile 修饰 resource
属性有什么问题?
双重检查加锁的简单示例:
public class DoubleCheckedLocking {private static Resource resource;public static Resource getInstance() {if (resource == null) { // LL01synchronized (DoubleCheckedLocking.class) {if (resource == null) {resource = new Resource();}}}return resource;}}
上述代码反编译后的字节码指令如下:
$ javap -c DoubleCheckedLocking.classCompiled from "DoubleCheckedLocking.java"public class net.coderbee.dcl.DoubleCheckedLocking {public net.coderbee.dcl.DoubleCheckedLocking();Code:0: aload_01: invokespecial #10 // Method java/lang/Object."<init>":()V4: returnpublic static net.coderbee.dcl.Resource getInstance();Code:0: getstatic #18 // Field resource:Lnet/coderbee/dcl/Resource;3: ifnonnull 356: ldc #1 // class net/coderbee/dcl/DoubleCheckedLocking8: dup9: astore_010: monitorenter11: getstatic #18 // Field resource:Lnet/coderbee/dcl/Resource;14: ifnonnull 2717: new #20 // class net/coderbee/dcl/Resource20: dup21: invokespecial #22 // Method net/coderbee/dcl/Resource."<init>":()V24: putstatic #18 // Field resource:Lnet/coderbee/dcl/Resource;27: aload_028: monitorexit29: goto 3532: aload_033: monitorexit34: athrow35: getstatic #18 // Field resource:Lnet/coderbee/dcl/Resource;38: areturnException table:from to target type11 29 32 any32 34 32 any}
最重要的是下面这几行,Java 里创建一个对象包含三个步骤:分配内存、执行初始化、赋值给引用。
17: new #20 // class net/coderbee/dcl/Resource20: dup21: invokespecial #22 // Method net/coderbee/dcl/Resource."<init>":()V24: putstatic #18 // Field
由于重排序的原因,可能在初始化之前就把对象赋值给引用,导致访问的线程看到一个未初始化完成的对象。这就是双重检查加锁不用 volatile 的致命问题。
《Java并发编程实践》里提到的一些可能重排序的场景:
编译器生成的指令顺序可以与源码中的顺序不同。
编译器会把变量保存在寄存器中,而不是内存中。
处理器可以采用乱序或并行等方式来执行指令。
缓存可能会改变将写入变量提交到主内存的顺序。
保存在处理器本地缓存中的值,对其他处理器不可见。
结合 DoubleCheckedLocking
代码来看看什么情况下就会出现上述问题:
加入线程A调用 getInstance
方法,发现 resource 引用是空的,就进入同步块,再次判断发现 resource 还是为空,开始创建对象的第一步 分配内存,由于重排序,把新对象的赋值给了 resource,并刷新到了主存;此时另一个线程B 也调用 getInstance
方法,发现 resource 引用非空,直接开始访问 resouce 对象,它访问的对象可能是未初始化完成的。
加上 volatile 可能禁止指令重排序,要求给引用赋值必须在对象初始化完成之后。
对于延迟初始化,推荐的是资源占位符类模式,示例如下:
public class ResourceFactory {static class ResourceHolder {static Resource resource = new Resource();}public static Resource getInstance() {return ResourceHolder.resource;}}
欢迎关注我的微信公众号: coderbee笔记 。

文章转载自coderbee笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




