引言
死锁是操作系统和并发编程中一个重要的概念,它指的是两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象,若无外力作用,它们都将无法推进下去。
一、死锁的定义与产生条件
1.定义:
死锁是指集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。换句话说,此时执行程序中两个或多个进程发生永久堵塞(等待),每个进程都在等待被其他进程占用并堵塞了的资源。(比如说你去面试,面试官说解释一下死锁我就给你offer,你对面试官说给我offer我就给你解释死锁。现在,你和面试官都陷入了等待状态。因为你们都需要对方释放的资源(回答问题或给offer)才能继续下一步,这种情况就是死锁。)
2.产生条件:
死锁的产生必须同时满足以下四个条件:
互斥条件:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
循环等待:线程之间相互等待
二、死锁的预防与避免
1.预防死锁:
破坏死锁产生的四个必要条件之一,就可以预防死锁的发生。
破坏互斥条件:允许某些进程(线程)同时访问某些资源,但有的资源(如打印机等)不允许同时被访问。
破坏占有且等待条件:可以实行预先分配策略,即进程在运行前一次性地向系统申请它所需要的全部资源。如果当前进程所需的全部资源得不到满足,则不分配任何资源。只有当系统能够满足当前的全部资源需求时,才一次性将所有申请的资源全部分配给该进程。但这种方法会降低资源利用率和进程的并发性。
破坏不可抢占条件:允许进程强行从占有者那里夺取某些资源。但这种方法实现起来困难,且会降低系统性能。
破坏循环等待条件:实行资源有序分配策略。采用这种策略即把资源事先分类编号,按号分配。所有进程对资源的请求必须严格按资源需要递增的顺序提出。进程占用小号资源,才能申请大号资源,就不会产生环路。这种方法与前面的策略相比,资源的利用率和系统吞吐量都有很大提高。
2.避免死锁:
在资源分配过程中,通过某种方法防止系统进入不安全状态,从而避免死锁的发生。常用的方法有银行家算法等。
三、死锁的检测与解除
1.检测死锁:
通过系统提供的工具或算法来检测系统中是否存在死锁。例如,在Java中,可以使用jps和jstack命令配合查看死锁问题。
2.解除死锁:
一旦检测到死锁,就需要采取相应的措施来解除死锁。常用的方法有:
资源剥夺法:挂起某些死锁进程,并抢占它的资源,将这些资源分配给其他的死锁进程。但应防止被挂起的进程长时间得不到资源,而处于资源匮乏的状态。
撤销进程法:强制撤销部分、甚至全部死锁进程并剥夺这些进程的资源。撤销的原则可以按进程优先级和撤销进程代价的高低进行。
进程回退法:让一(多)个进程回退到足以回避死锁的地步,进程回退时自愿释放资源而不是被剥夺。要求系统保持进程的历史信息,设置还原点。
四、死锁示例
以下是用Java编写的一个简单死锁示例:
public class a {
public static void main(String[] args) {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
System.out.println("Thread t1: lock A");
try { Thread.sleep(1000); } catch (InterruptedException e) {} // 让线程t2有机会获取到B锁
synchronized (B) {
System.out.println("Thread t1: lock B");
System.out.println("Thread t1: 操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
System.out.println("Thread t2: lock B");
try { Thread.sleep(1000); } catch (InterruptedException e) {} // 让线程t1有机会获取到A锁
synchronized (A) {
System.out.println("Thread t2: lock A");
System.out.println("Thread t2: 操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
运行上面的代码,会出现死锁现象,线程t1和t2会互相等待对方释放锁,从而导致程序无法执行。