什么是死锁?


什么是死锁?

  1. 发生在并发
  2. 互不相让:当两个(或更多)线程(或进程)相互持有对方所需要的资源,又不主动释放,导致所有人都无法继续前进,导致程序陷入无尽的阻塞,这就是死锁。
  3. 死锁图解
    死锁
  4. 多个线程造成死锁的情况:如果多个线程之间的依赖关系是环形,存在环路的锁的依赖关系,那么也可能会发生死锁。
  5. 几率不高但危害大
    • 不一定发生,但遵守“墨菲定律
    • 一旦发生,多是高并发场景,影响用户多
    • 整个系统奔溃,子系统崩溃、性能降低
    • 压力测试无法找出所有潜在的死锁

死锁的4个必要条件

  1. 互斥条件:一个资源每一次只能被一个进程或者线程使用。
  2. 请求与保持条件:第一个线程请求第二把锁同时保持第一把锁,这个时候,线程请求的时候已经自身阻塞了,就是说对于已经获取的资源保持不变,不会释放。
  3. 不剥夺条件:不被外界干扰剥夺锁。
  4. 循环等待条件:两个或者多个互相等待或者环形等待锁的释放

如何定位死锁?

修复死锁的策略

  1. 避免策略哲学家就餐的换手方案、转账换序方案。
  2. 检测与恢复策略:一段时间检测是否有死锁,如果有就剥夺某一个资源,来打开死锁。
    • 允许发生死锁
    • 每次调用锁都记录
    • 定期检查“锁的调用链路图”中是否存在环路
    • 一旦发生死锁,就用死锁恢复机制进行恢复
  3. 鸵鸟策略:鸵鸟这种动物在遇到危险的时候,通常就会把头埋在地上,这样一来它就看不到危险了。而鸵鸟策略的意思就是说,如果我们发生死锁的概率极其低,那么我们就直接忽略它,直到死锁发生的时候,再人工修复。这是一种消极的处理方式,不推荐。

实际工程中如何避免死锁

  1. 设置超时时间
    • Lock的tryLock(long timeout, TimeUnit unit)
    • synchronized不具备尝试锁的能力
    • 造成超时的可能性多:发生了死锁、线程陷入死循环、线程执行很慢
    • 获取锁失败:打日志、发报警邮件、重启等
  2. 多使用并发类而不是自己设计锁
    • ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等
    • 实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高
    • 多用并发集合少用同步集合,并发集合比同步集合的可扩展性更好
    • 并发场景需要用到map,首先想到用ConcurrentHashMap
  3. 尽量降低锁的使用粒度:用不同的锁而不是一个锁
  4. 如果能使用同步代码块,就不使用同步方法:自己指定锁对象(方便掌控锁)
  5. 给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵循这个最佳实践
  6. 避免锁的嵌套:MustDeadLock类(互相转账问题)
  7. 分配资源前先看能不能收回来:银行家算法
  8. 尽量不要几个功能用同一把锁:专锁专用

其他活性故障

  1. 死锁是最常见的活跃性问题,不过除了刚才的死锁外,还有些类似的问题,会导致程序无法顺利执行,统称为活跃性问题
  2. 活锁(LiveLock)
    • 虽然线程并没有阻塞,也始终在运行(所以被称为“活锁”,线程是“活的”),但是程序却得不到进展,因为线程始终重复做同样的事
    • 如果是死锁,只会是等待,不会消耗CPU资源
    • 活锁演示:牛郎织女的幸福生活
    • 如何解决活锁问题
      • 原因:重试机制不变,消息队列始终重试,吃饭始终谦让
      • 以太网的指数退避算法:连接重试时间不是固定的,而是随机的,并且随机范围随着碰撞强度的升高而逐渐扩大
      • 加入随机因素:牛郎织女的幸福生活
    • 工程中的活锁实例:消息队列
      • 策略:消息如果处理失败,就放在队列开头重试
      • 由于依赖服务出了问题,处理该消息一直失败
      • 没阻塞,但程序无法继续
      • 解决方案:放到队列尾部、增加重试次数
  3. 饥饿
    • 当线程需要某些资源(例如CPU),但是却始终得不到
    • 线程的优先级设置的过于低,或者有某线程持有锁同时又无限循环不释放锁,或者某程序始终占用某文件的写锁
    • 饥饿可能会导致响应性差:比如,我们的浏览器有一个线程负责处理前台响应(打开收藏夹等动作),另外的后台线程负责下载图片和文件、计算渲染等。在这种情况下,如果后台线程把CPU资源都占用了,那么前台线程将无法得到很好地执行,这会导致用户的体验很差

常见面试题

写一个必然死锁的例子,生产中什么场景下会发生死锁?

死锁例子: 银行转账问题
什么场景容易发生死锁:最明显的就是一个方法中获取多个锁,或者在一个方法调用链中获取多个锁。

发生死锁必须满足哪些条件?

本篇提到。

如何定位死锁?

本篇提到。

有哪些解决死锁问题的策略?

本篇提到,同时还有两篇死锁解决案例,银行转账问题哲学家就餐问题

讲一讲哲学家就餐问题?

哲学家就餐问题

实际工程中如何避免死锁?

本篇提到。

什么是活跃性问题?活锁、饥饿和死锁有什么区别?

本篇提到。

笔记来源:慕课网悟空老师视频《Java并发核心知识体系精讲》

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发飙的蜗牛咻咻咻~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值