面试中的死锁
概念
多个线程各自占有一些公共资源并相互等待其他线程占有的资源,而导致线程一直在等待对方释放资源。
形成的四个条件:
- 互斥条件
- 持有并请求
- 不可剥夺
- 环路等待
解决方法:破坏环路等待条件
- 固定加锁顺序
- 使用定时锁:使用显示Lock锁,在获取锁时使用tryLock()方法。当等待超过实现的时候,tryLock()不会一直等待
算法层面:
银行家算法
思想:在资源分配之前,预先判断这次分配是否会导致系统进入不安全状态。
数据结构:
- 长度为m的一维数组Available表示还有多少可用的资源
- n*m矩阵Max表示各进程对资源的最大需求数
- n*m矩阵Allocation标识已经分配给各进程分配了多少资源。
- Max - Allocation = Need矩阵表示最多还需要多少资源
- 用长度为m的一位request表示进程此次申请的各种资源数
步骤:
- 检查此次申请是否超过了之前声明的最大需求数
- 检查此时系统剩余可用资源是否能够满足这次请求
- 试探着分配,更改各数据结构
- 用安全性算法检查此次分配是否会导致系统进入不安全状态
安全性算法步骤:
- 检查当前的剩余资源是否能满足某个进程的最大需求,如果可以,则进入安全序列,并把该进程所持有的全部资源回收。
- 不断重复上述过程,看最终能否把所有进程都加进安全序列。
MySQL中常见的死锁以及原因
- 事务之间对资源访问顺序的交替 出现原因: 一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。
解决方法: 这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源
- 并发修改同一记录 出现原因:主要是由于没有一次性申请够权限的锁导致的。
用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项目中经常发生。
解决方法:
a. 乐观锁,实现写-写并发
b. 悲观锁:使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
- 索引不当导致的死锁 出现原因: 如果在事务中执行了一条不满足条件的语句,执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。
解决方法:
SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。