有一个笑话,在疫情期间,某地的健康码突然崩溃,无法打开,于是维护健康码的程序员火速赶去公司想维护健康码,但是到了公司楼下,被保安大爷拦下
大爷:得看见正常的健康码才能进去
程序员:我得进去将健康码修复了才能给你看
大爷:那不行,得看见健康码才能让你进去
程序员:…
这明显是出现了矛盾,而这样的互不相让的情况在计算机线程中就是死锁
死锁
在多线程编程中,我们为了防止多线程竞争共享资源而导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。
那么,当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。(可以借用上面的例子来理解)
死锁只有同时满足下列四个条件才能触发
- 互斥:资源处在非共享模式,一次只能一个线程使用,直到释放后,其他线程才能使用
- 持有并等待:一个线程至少持有一个资源,并等待另一个资源,此资源被其他线程占用
- 不可剥夺:资源无法被抢占,只有当释放后,资源才能被其他线程使用
- 循环等待:在一组线程中[p0,p1,p2],p0等待p1释放资源,p1等待p2释放资源,p2等待p0释放资源。
死锁样例代码
public static void main(String[] args) {
Object clocker1=new Object();
Object clocker2=new Object();
Thread t1=new Thread(()->{
synchronized (clocker1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t1 等待");
synchronized (clocker2){
System.out.println("t1 拿到clocker2资源 ");
}
}
});
Thread t2=new Thread(()->{
synchronized (clocker2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("t2 等待");
synchronized (clocker1){
System.out.println("t2 拿到clocker1资源 ");
}
}
});
t1.start();
t2.start();
}
避免死锁的方法
解决死锁的方法可以从多个角度去分析,一般的情况下,有预防,避免,检测和解除四种。
- 预防 是采用某种策略,限制并发进程对资源的请求,从而使得死锁的必要条件在系统执行的任何时间上都不满足。
- 避免则是系统在分配资源时,根据资源的使用情况提前做出预测,从而避免死锁的发生
- 检测是指系统设有专门的机构,当死锁发生时,该机构能够检测死锁的发生,并精确地确定与死锁有关的进程和资源。
- 解除 是与检测相配套的一种措施,用于将进程从死锁状态下解脱出来。
这里博主水平有限,只能给出一篇javaguide的相关博客,大家可以自行查看:操作系统常见面试题总结(上) | JavaGuide