关于双重检查加锁(DCL)的理解

双重检查加锁的一般形式:

class SomeClass {
    private Resource resource = null;
    public Resource getResource() {
        if (resource == null) {
            synchronized {
                if (resource == null)
                    resource = new Resource();
            }
        }
        return resource;
    }
}

援引java并发编程实战的解释:

 

关于双重检查加锁(DCL)的理解_第1张图片

关于双重检查加锁(DCL)的理解_第2张图片

java并发编程实战在这个地方解释的有点乱,从这个章节蹦到别的章节。。。

确实,正如她所说,问题的关键在于,线程可能看到一个仅被部分构造的Resource;具体解释如下:

1.首先想想前面给的Happens-Before原则,关于双重检查加锁(DCL)的理解_第3张图片

此原则用于检测某操作是否能进行重排序,而重排序是编译器对JVM字节码进行重排序的操作在这里关于“操作”两个字定义的很模糊(相当于没有定义),那什么是操作呢,什么又是一个不可再分的原子操作呢;对JVM层面来说,一个JVM字节码就是一个对于JVM执行指令不能再分的原子操作,且上下字节码的操作结果完全互相可见,再往下可能会有更细粒度的字节码以及机器指令,但是现在只讨论JVM字节码;在java语言中,对基本变量int char等的读写是一个原子操作,编译成字节码只有一条(这才是原因),因此很多书上经常写关于基本变量的读写操作来作为例子,很久之前我还以为一条java代码是一个原子操作。。。

在这里定义的操作的含义就是一条java代码(有时候还需要根据具体语境具体分析其到底指的是什么),对于对象类型的new

关于双重检查加锁(DCL)的理解_第4张图片

可以看到上述仅仅一个new Object()对象都有好几条JVM字节码与之对应,更别说一个复杂耗时的大对象的new或者初始化了,上面讲到,当两个操作不满足Happens-before时,JVM将对其进行重排序,对于最上面的那个DCL例子而言,线程A最先进来发现resource为null,因此进入同步块,再次观测resource==null,此时进行初始化,接下来的操作JVM可能会对其进行重排序,

我们不妨假设是这样一种执行顺序,1.为对象分配内存 2.调用构造函数 3.初始化成员变量 

再假设当操作1完成时,发生了线程切换(这个例子很极端,一般不会只执行一条指令就会发生切换,在此只为说明情况而设),线程B进入了此方法,他首先观察resource,发现resource==null为false,为什么呢?想想==含义,其内部是判断对象地址是不是null,此时已经为其分配了内存,有了地址,此时将会resource==null判断为true了,接着线程B将会返回一个尚未被构造完成的resource引用,此时DCL broken....

接着java并发编程实战中说将resource域声明为volatile的,私以为这样做也是不怎么可取的,因为volatile这个东西只是说对读写操作的前后可见性,但是没说对volatile声明的对象其成员属性也是这样的,除非你声明的volatile对象其内部所有属性都是volatile的,你可以一试。而且这个关键字很多博客说是依赖于JVM的,所以不要对其做出过多假设(虽然大多数人都是一个JVM平台的);毕竟这本书也给了相应的改进措施-延长初始化占位类模式,为什么不用呢///

对本文有帮助的帖子,比较老了,不确定是否适用当前版本(某种编译器对DCL做了某种优化或者JVM底层实现改变都说不定),但意义上是对的

https://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html

--                                              分                割                     线                   --

更正:

上面说,一条JVM指令对JVM来说是一个原子操作这句话是不严谨的,根据《深入了解JVM》这本书,即时编译出来一条字节码指令,也并不意味着这条指令就是一个原子操作,一条字节码指令在解释执行时,解释器将要运行许多代码才能实现它的语义,如果是编译执行,一条字节码指令也可能转化成若干条本地机器码指令,但是此处使用字节码(某个操作都能分成好几个字节码指令,一定是非原子操作)已经能说明问题。

如有问题或者部不对的地方欢迎指正,本人QQ 2900250200

你可能感兴趣的:(关于双重检查加锁(DCL)的理解)