线程死锁
Posted jiatcode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程死锁相关的知识,希望对你有一定的参考价值。
线程死锁
1.死锁
-
多个线程因竞争资源而造成的一种僵局(互相等待),无外力作用下程序无法推进的情况称之为死锁
-
如下图:线程P1拥有锁R1,请求锁R2,而线程P2拥有锁R2请求锁R1,彼此都请求不到资源,结束不了方法无法释放对方需要的资源,因此相互等待无法推进,这就是死锁
2.产生的四个必要条件
1. 互斥条件
? 进程要求对所分配的资源进行排他性控制,即该资源只能被一个进程占用,其他请求的进程只能等待占用资源的进程结束,释放资源
2.不可剥夺条件
? 进程已经获取了一个资源,在它使用完毕之前,无法被其他进程剥夺走,只能由获取该资源的进程主动释放资源
3.请求与保持条件
? 进程当前已经获取了一个资源,但又提出了一个新的资源请求,而新的资源被占用了,此时请求被阻塞, 当前获取的的资源也无法释放
4.循环等待条件
? 多个进程呈环形互相等待的情况称为循环等待,出现死锁的时候一定是循环等待的情况,但是循环等待不一定就是死锁,但这个闭环中的某个进程请求的资源不仅仅只有一个请求途径,环形外有进程也释放它请求的资源,则可以跑出闭环,则不是死锁
3.死锁演示
以下实现一个死锁:
-
通过两个线程互相等待的情况来演示
public class Locks implements Runnable{ private int flag; //用来引导两个线程调用不同的资源 //这里要对两个锁定义static,必须是请求共享资源才会引发死锁 public static Lock lock = new ReentrantLock(); public static Lock lock2 = new ReentrantLock(); public Locks(int flag){ this.flag = flag; } public void run() { if (flag==1) { //请求锁1 lock.lock(); try { System.out.println("线程1请求锁2"); //持有锁1的时候请求锁2 lock2.lock(); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); lock2.unlock(); } }else{ lock2.lock(); try { System.out.println("线程2请求锁1"); lock.lock(); Thread.sleep(1000); } finally { lock.unlock(); lock2.unlock(); } } } }
-
创建启动线程进行测试:
public class Demo { public static void main(String[] args) { Thread thread = new Thread(new Locks(1)); Thread thread2 = new Thread(new Locks(2)); thread.start(); thread2.start(); } }
-
发生死锁
4.处理死锁
- 预防死锁:通过设置限制条件,破坏死锁产生的四个必要条件之一就可以预防
- 避免死锁:在资源的动态分配过程中,想办法防止系统进入不安全状态(比如两个线程不请求共享的资源)
- 检测死锁:允许系统发生死锁,但可以设置检测机构即使检测出死锁并采取适当措施解除,该方法操作系统比较常用,他解决了预防死锁和避免死锁会造成的系统处理能力下降,资源利用率降低,保证系统效率的情况下有效处理死锁
- 接触死锁:检测出死锁后使用某种措施将进程从死锁状态中解脱出来
1.预防
- 破环请求与保持条件
- 一次性分配资源,进程获取资源时要获取所有需要的资源,满足条件则分配,不满足则都不分配
- 要求进程请求新的资源S时要先释放持有的资源R,无论他是不是很快就用到资源R
- 破坏不可剥夺条件 -- 则资源允许被抢夺
- 如果进程持有资源的情况下请求新的资源被拒绝,那就要释放当前持有的资源,有需要再重新请求
- 在两个进程的优先级不一样的情况下,进程一占有进程二请求的资源,操作系统可以抢占进程一的资源,要求他释放,以让进程二请求到资源
- 破坏循环等待
- 将系统中的所有资源统一编号,进程在可在任何时候提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。
2.避免
死锁的预防是严格控制产生死锁的必要条件的存在,而避免是不严格控制,因为就算满足了必要条件也不一定会发生死锁,如果这样限制的话,系统的性能将会降低,因此通过一些更优的算法,来避免产生死锁,就算条件满足也不会发生死锁。
1. 有序资源分配法
-
为所有资源统一编号,所有的线程在请求不同资源时都得按顺序请求
-
同类资源则要一次请求完毕,即同一台设备的机器打印机传真机要同时申请
-
举个例子:
现有两个进程P1,P2,两个资源R1,R2,P1和P2都要请求R1,R2,假设P1先获取到了R1,那么P2就不能先去请求R2,必须等待P1释放R1资源,以此来避免死锁
2.银行家算法
银行家算法结构的逻辑如下:
分析:
- 主要涉及几个数值:系统可分配资源(available[j])、进程需要的资源(need[i,j])、进程获取的资源(allocation[i,j])、进程请求的资源(Request[j])
- 进程开始请求时,会先判断进程请求的资源是不是在进程需要的资源范围内,不是则报错中断请求,然后再进行一个判断,进程请求的资源是否小于或等于系统可分配资源,也就是确认pi请求的资源系统能否分配。
- 满足以上两个条件后系统会尝试分配资源给进程,计算出分配后的各个数值
- 再根据算法进行安全性检测,此处的算法先不做说明,等博主学习后会单独写随笔讲讲算法
- 符合规定就直接分配,不符合的将撤回尝试操作,恢复资源,pi等待
3.顺序加锁
- 顺序加锁的方法与有序资源法的思路一样,只是它限制的对象时锁Lock,而不是资源
- 但是该资源只适用于特定场景,因为这种方式需要事先知道所有有可能会用到的锁,但总有些是不法预料到的
4.限时加锁
- 限时加锁是线程在尝试获取锁的时候加上一个超时的时间,若超过这个时间还获取不到资源的话,就回退并释放已经请求的锁资源,进入等待,随机时间后继续尝试请求
- 缺点
- 当线程数量少时,该种方法可以有效避免死锁,但是当线程数量过多时,这些线程的加锁时限大概率是相同的,也是有可能出现一个不断超时重试的死锁
- java中不能对synchronized同步块设置超时时间,你需要创建一个自定义锁,或使用java5中的java.util.concurrent工具包
3.检测
? 预防和避免死锁系统开销大且不能充分利用资源,更好的方法是不采取任何限制性措施,而是提供检测和解脱死锁的手段,这就是死锁的检测和恢复
死锁检测的数据结构
- E是现有资源向量(existing resource vector):代码每种资源已经存在的资源总数(一个资源的数组)
- A是可用资源向量(available resource vector):那么ai表示当前可供使用的资源(一个资源的数组)
- C是当前分配矩阵(current allocation matrix):该矩阵的行代表一个进行,一个列代表一类资源,即第 i 行表示进程Pi 当前持有的资源总数
- R是请求矩阵(request matrix):R的第 i 行代表 Pi 所需要所需要的资源
死锁检测步骤
- 如图,算法会寻找一个正在请求资源,且系统可以为其分配的进程,则执行进程,结束后将资源释放添加到系统可分配资源的向量中,直到找不到这样的进程,算法结束,没有被标记的进程就是死锁线程
- 此处还没结束的进程是指在R矩阵中存在的行对应的进程
4.恢复
抢占
- 临时将某个资源从所属的进程中,转移到另一个进程
- 这个做法很需要人工干预,因为在系统中一般是不会主动的释放资源的除非进程结束,做法是否可行取决于资源本身的特性
回滚
- 周期性的对进程状态进行备份,发现死锁时根据备份恢复进程的状态到未获取所占资源的时刻,将释放的资源分配给其他死锁进程
杀死进程(不推荐)
- 直接将一个或者若干个进程杀死
- 尽量保证杀死的进程可以从头再来而不带来任何副作用
以上是关于线程死锁的主要内容,如果未能解决你的问题,请参考以下文章