请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

双重锁模式

为什么会获得构建到一半的connection,有点没听懂,如果没有构建完,就不会存在connection对象,下一个线程怎么会使用这个构建到一半的connection呢?

正在回答 回答被采纳积分+3

3回答

bbmouse 2021-10-31 17:35:11

听了老师的课,我说说我的理解:

首先,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正常顺序是:

  1. 内存中分配空间

  2. 执行构造方法初始化对象(往内存写数据)

  3. 把内存地址填到connect里

乱序后:

  1. 内存中分配空间

  2. 把内存地址填到connect里

  3. 执行构造方法初始化对象(往内存写数据)


上面说了,synchronized外面的判断,本来只是由于不满足happens-before关系,connect内存地址可能读到null,但现在由于乱序后步骤2先把内存地址填到了connect里,此时步骤3还没有执行完构造方法

线程2读connect引用,发现不是nukk,里面有内存地址,就去内存中访问对象,此时对象构造方法还没执行完,就有问题了


解决:加上volatile,这个关键字在这里的作用应该是禁止Java虚拟机优化编译指令(重排指令),而不是保证happens-before关系(已经由synchronized保证)。


对于这个题,问题关键应该和原子性啥的无关,主要是synchronized遵守了happens-before,happens-before保证了可见性,volatile禁止重排指令(它也可以保证可见性,但这里有synchronized已经可以保证)


0 回复 有任何疑惑可以回复我~
aalizwel 2018-12-25 21:24:05

如图上所说,创建对象很复杂,中间一不小心没有设置可见性,就被掉用了
https://img1.sycdn.imooc.com//szimg/5c222f300001a59106720504.jpg

https://img1.sycdn.imooc.com//szimg/5c222f3f0001007705040672.jpg

0 回复 有任何疑惑可以回复我~
aalizwel 2018-12-18 21:36:12

对象创建过程是分成几个步骤的,不是原子性的。比如:分配空间、初始化对象值、将对象指向空间,最关键的是这几个步骤间还有可能乱序重排。当一个线程走到分配空间的时候,第二个线程是可以访问到的但是没有初始化等几个步骤,这个对象是不可用的。

connection链接会更加复杂化,解决这个问题的方法就是添加volatile关键字让第二个线程可见第一个线程的操作

0 回复 有任何疑惑可以回复我~
  • 提问者 红邮筒 #1
    那麻烦问一下,为什么“当一个线程走到分配空间的时候,第二个线程是可以访问到的”,这又和乱序重排有什么关系
    回复 有任何疑惑可以回复我~ 2018-12-21 11:15:48
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信