JavaEE多线程中的 死锁 可重入锁和内存可见性问题


死锁 可重入锁

一个线程,连续针对一把锁,加锁两次,就可能造成死锁
在这里插入图片描述
形如上述代码,一个线程针对一把锁,连续枷锁两次,第一次加锁,能够加锁成功
第二次加锁,就会加锁失败(锁已经被占用)

就会在第二次加锁这里阻塞等待,等到第一把锁被解锁,第二次加锁才能成功!
第一把锁解锁,则要求执行完synchronized代码块,也就是要求第二把锁能加锁成功!

针对上述情况,不会产生死锁的话,这样的锁就叫做可重入锁
针对上述情况,会产生死锁,这个锁叫做不可重入锁

synchronized这个锁就是可重入的!

可重入锁底层实现,只需让让锁里面记录好是哪个线程持有的这把锁就可以了!

t线程尝试针对this来加锁~~this这个锁里面就记录了是t线程持有了它
第二次进行加锁的时候,锁发现还是t线程,就直接通过了,不会阻塞等待!

那怎么知道执行到加锁代码,要不要真正的解锁吗?

引入一个计数器!!
每次加锁,计数器++
每次解锁,计数器–;
如果计数器为0,此时的加锁操作才真加锁
同样,计数器为0,此时的解锁操作才真解锁

💞可重入锁的实现要点:

  1. 让锁里持有线程对象,记录是谁加了锁
  2. 维护一个计数器,用来衡量什么时候是真加锁,啥时候是真解锁,啥时候是直接放行

如果程序抛出异常,没人catch,就脱离了之前的代码块,脱离一层代码块,计数器就-1,脱离多层代码块,减到0了也就解锁了~

加锁代码中出现异常不会死锁的~无论如何解锁代码都能执行到的!

这也是把synchronized设计成以一个关键字的原因~
结合代码块来做解锁操作,无论是正常执行完代码块,还是异常执行完代码块,都会触发解锁操作!

内存可见性问题

💦内存可见性问题:在编译器优化的背景下,一个线程把内存给改了,另一个线程并不能及时的感知到!

例如:

  • 一个线程负责读数据(LOAD),一个线程负责修改数据,假设,读操作非常频繁

在这里插入图片描述
当频繁进行多次读内存(LOAD)和进行比较(CMP)时,程序就不再每次循环都重新读取内存,而是只读一次,后续的读内存则简化成直接读寄存器!

但这样是有问题的!
编译器对于多线程代码的判定并没有那么准,当其他线程把当前内存中的数据进行改动的话,编译器会出现误判操作~
比如我把上面的代码运行:在这里插入图片描述
❗线程没有结束!

💌解决方案:避免让编译器做出"不读内存"的优化! 优化的目的是为了算的快,但前提是算得准!!!

解决内存可见性问题的方案

volatile “可变的” : 可以用这个关键字来修饰一个变量,此时被修饰的变量,编译器就不会做出"不读内存"的优化
在这里插入图片描述

但是并不保证原子性~
针对一个线程读,一个线程修改,这个场景,使用volatile是合适的~
但是根据两个线程同时修改,volatile是起不到作用的!

volatile禁止了编译器优化,避免了直接读取CPU寄存器中(工作内存)缓存的数据,而是每次都重新读内存(主内存)!

站在JVM的角度来看待volatile :

正常程序执行的过程中,会把主内存的数据,先加载到工作内存中,再进行计算处理
编译器优化可能会导致不是每次都真的读取主内存,而是直接去工作内存中的缓存数据(就可能导致内存可见性)


volatile起到的效果,就是保证每次读取内存都是从主内存重新读取


此处工作内存(cpu寄存器)并不是真的内存!!主内存才是真的内存!

❓为什么说:"工作内存“和硬件无关?
CPU内部的结构是不一样的~
有的CPU只带寄存器
有的CPU除了寄存器,还带缓存
有的CPU缓存还有多级缓存(L1,L2,L3)
所以不同的CPU方式不一样,此时使用"工作内存"就指上面的全部(寄存器+缓存)!
JVM通过了一个更高层次的抽象来简化了实际的硬件数据交互过程,并且更加的宏观,不管是什么样的CPU都可以叫做"工作内存”!!

总结

在这里插入图片描述

你可以叫我哒哒呀
本篇到此结束
“莫愁千里路,自有到来风。”
我们顶峰相见!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值