就好比,小情侣们每天都要让对方说爱自己,究竟谁更爱谁就产生了死锁,哈哈哈哈哈。解决死锁的办法就是互相给对方戴green帽
死锁
两个或两个以上的进程因为争夺资源造成的一种等待的过程就是死锁,如果没有外力干涉,就不可能再进行下去。
代码演示:
package com;
public class DeadLock {
//创建两个对象
static Object o1=new Object();
static Object o2=new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"当前持有锁o1,试图获取锁o2");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"获取锁o2");
}
}
},"A").start();
new Thread(()->{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"当前持有锁o2,试图获取锁o1");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"获取锁o1");
}
}
},"B").start();
}
}
结果:
A当前持有锁o1,试图获取锁o2
B当前持有锁o2,试图获取锁o1
线程互相调用,无法继续调用。
验证是否是死锁:
- jps ------>类似于Linux ps -ef
- jstack jvm自带堆栈跟踪工具
C:\Users\小胡\Desktop\untitled>jps
17312 Launcher
15476 Jps
14920
15208 DeadLock
9628 SyncLockDemo
C:\Users\小胡\Desktop\untitled>jstack 15208
2022-03-25 22:50:02
Full thread dump OpenJDK 64-Bit Server VM (25.292-b10 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000002a37d49c800 nid=0x4430 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"B" #13 prio=5 os_prio=0 tid=0x000002a316200800 nid=0x1738 waiting for monitor entry [0x000000d69d9fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.DeadLock.lambda$main$1(DeadLock.java:22)
- waiting to lock <0x00000000d61a36c8> (a java.lang.Object)
- locked <0x00000000d61a36d8> (a java.lang.Object)
at com.DeadLock$$Lambda$2/1078694789.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
....
死锁产生的必要条件:
- 互斥条件:只有对必须互斥使用的资源的争夺才会导致死锁
- 不剥夺条件:进程所获得的资源在未使用完之前,不能有其他进程强行夺走,只能主动释放
- 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求的进程被阻塞,但又对自己已有的资源保持不放。
- 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程请求。
什么时候会发生死锁:
- 对系统资源的竞争,
- 进程推进顺序非法
- 信号量的使用不当也会导致死锁
预防死锁
①破坏互斥条件
互斥条件:只有对必须互斥使用的资源的争夺才会导致死锁
如果把只能互斥使用的资源改造成允许共享使用,则系统更不会进入死锁状态。 该策略的缺点:并不是所有的资源都可以改造成共享使用的资源。并且为了系统安全,很多地方还必须使用这种互斥性。因此很多时候都无法破环互斥条件。
②破坏不剥夺条件
不剥夺条件:进程所获得的资源在未使用完之前,不能有其他进程强行夺走,只能主动释放
破坏不剥夺条件:
方案一:当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏不可剥夺原则
方案二:当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度算法,就是将处理机资源强行剥夺给优先级更高的进程使用)
该策略的缺点:
- 实现起来比较复杂
- 释放以获得的资源可能造成前一段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU
- 反复的申请和释放资源会增加系统开销,降低系统吞吐量
- 若采用方案一,意味着只要暂时得不到某个资源,之前获得的哪些资源就都需要放弃,以后在重新的申请,如果一直发生这样的事,就会导致进程饥饿
③破坏请求和保持条件
请求和保持条件:进程已经保持了至少一种资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
可以采用静态分配策略,即进程在运行前一次性申请完它所需要的全部资源,在他的资源为满足前,不让它投入运行。一旦有投入运行后,这些资源就一直归他所有,该进程就不会在请求别的任何资源了
优点:该策略实现起来比较简单
缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期都一直保持这所有资源,就会造成严重的资源浪费,资源利用率很低。另外,该策略也有可能导致某些进程饥饿。
④破坏循环等待条件
循环等待条件:存在一种进程资源的循坏等待链,链中的每一个进程已获得的资源同时被下一个进程所使用。
可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按照标号递增的顺序请求资源,同类资源(及标号相同的资源)一次申请完
原理分析:一个进程只有已占有小标号的资源时,才有资格申请更大的资源,按此规则,已持有大标好的进程不可能逆向申请小标号的资源,从而就不会产生循环等待的现象
该策略的缺点:
- 不方便增加新的设备,因为可能需要重新分配所有的标号
- 进程实际使用资源的顺序可能和标号递增顺序不一致,会导致资源浪费
- 必须按规定次序申请资源,用户编程麻烦
|