听了老师的课,我说说我的理解:
首先,Java内存模型定义了happens-before关系,这个关系可以保证:如果操作具有顺序性,后面执行的能看见前面执行的结果(内存中的)
虚拟机需要知道什么时候遵守这个关系,这些情况就是老师视屏列举的一些操作,比如synchronized
再看双重锁模式:
首先线程1获取锁,执行synchronized里面的操作,然后释放锁,接着线程2拿到同一把锁进入synchronized,这就是“unlock发生在lock之前”的意思,所以此时线程2肯定看到了最新的connect,synchronized保证了可见性,也就是遵循了happens-before关系,这个没毛病;
但如果线程1在执行,还没释放锁,线程2此时在synchronized之外,这个情况还不满足“unlock发生在lock之前”,所以此时线程2没有可见性保证,也就是不遵守happens-before关系,所以线程2读到的connect不是最新值,可能读到老的值,也就是null,但是对于此题,读到null没有问题,if == null, 线程2会继续等着锁
到此可以说明:单独加synchronized可以让虚拟机遵守happens-before关系,但是Java虚拟机由于编译的一些指令优化,会对new 对象的底层指令进行乱序(编译后底层有好几条指令),这属于另一个问题
new正常顺序是:
内存中分配空间
执行构造方法初始化对象(往内存写数据)
把内存地址填到connect里
乱序后:
内存中分配空间
把内存地址填到connect里
执行构造方法初始化对象(往内存写数据)
上面说了,synchronized外面的判断,本来只是由于不满足happens-before关系,connect内存地址可能读到null,但现在由于乱序后步骤2先把内存地址填到了connect里,此时步骤3还没有执行完构造方法
线程2读connect引用,发现不是nukk,里面有内存地址,就去内存中访问对象,此时对象构造方法还没执行完,就有问题了
解决:加上volatile,这个关键字在这里的作用应该是禁止Java虚拟机优化编译指令(重排指令),而不是保证happens-before关系(已经由synchronized保证)。
对于这个题,问题关键应该和原子性啥的无关,主要是synchronized遵守了happens-before,happens-before保证了可见性,volatile禁止重排指令(它也可以保证可见性,但这里有synchronized已经可以保证)