什么是死锁
在两个或者两个以上的线程运行中,线程之间互相调用彼此所拥有的资源时,造成的线程一直等待的问题。
如下图,线程1与线程2彼此调运各自的资源
代码示例
import java.util.concurrent.TimeUnit;
/**
* 死锁的示例
*/
public class ThreadDemo36 {
public static void main(String[] args) {
// 声明(加锁的)资源
Object lockA = new Object();
Object lockB = new Object();
// 创建线程 1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// 获取当前线程名称
String threadName = Thread.currentThread().getName();
synchronized (lockA) {
// 已经获取到 lockA
System.out.println(threadName + " Get lockA.");
try {
// 休眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " Waiting lockB.");
synchronized (lockB) {
System.out.println(threadName + " Get lockB.");
}
}
}
}, "t1");
// 启动线程
t1.start();
// 创建线程 2
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
// 1.获取资源 B(lockB)
synchronized (lockB) {
System.out.println(threadName + " Get lockB.");
try {
// 2.休眠 1s(为了等待让 t1 先得到 lockA)
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadName + " Waiting lockA.");
// 3.获取资源 A(lockA)
synchronized (lockA) {
System.out.println(threadName + " Get lockA.");
}
}
}
}, "t2");
// 启动线程 t2
t2.start();
}
}
产生死锁的条件
1.互斥条件:
当资源被一个线程拥有之后,就不能被其它的线程拥有了。
⒉.请求拥有条件:
当一个线程拥有了一个资源之后又试图请求另一个资源。
3.不可剥夺条件:
当一个资源被一个线程拥有之后,如果不是这个线程主动释放此资源的
情况下,其他线程不能拥有此资源。只能等待!
4.环路等待条件:
两个或两个以上的线程在拥有了资源之后,试图获取对方资源的时候形成
了一个环路(可以连成一个回路)。
解决死锁的放式
预防死锁(死锁发生前)(破坏产生死锁的四个条件)
只要(互斥条件,请求拥有条件,不可剥夺条件,环路等待条件)这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享资源所必须的,不仅不能更改,所以,主要是破坏产生死锁的其他三个条件。
1、破坏“请求拥有”条件
方法一:在所有的进程开始运行之前,必须一次性地申请其在整个运行过程中所需要的全部资源。
优点简单易实施且安全。
缺点:如果某项资源不满足,进程无法启动,而其他已经满足了的资源也不会被其他进程得到利用,严重降低了资源的利用率,造成资源浪费。使进程经常发生饥饿现象。
方法2:该方法是对第一种方法的改进,允许进程只获得运行初期需要的资源(部分资源),便开始运行,在运行过程中逐步释放掉分配到的已经使用完毕的资源,然后再去请求新的所需要的资源。这样的话,资源的利用率会得到提高,也会减少进程的饥饿问题。
2、破坏“不可剥夺”条件
当一个进程已经拥有了一些资源,再提出新的资源请求且没有得到满足时,它必须释放掉自己已经获得的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
这种方法实现起来比较复杂,而且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。(不建议)
3、破坏“环路等待”条件
控制资源申请的顺序,即通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请的资源只能申请编号大于i的资源。
避免死锁(死锁发生前)(避免形成环路)
死锁避免就是利用额外的检验信息,在分配资源时判断是否会出现死锁的情况,只有不会出现死锁的情况,才分配资源。
- 1、如果一个进程的请求会导致死锁,则不启动该进程
- 2、如果一个进程的增加资源请求会导致死锁 ,则拒绝该申请。
避免死锁的具体实现通常利用银行家算法
死锁的解除(死锁发生时)
- 1、抢占资源:从一个或多个进程中抢占足够数量的资源分配给死锁进程,从而解除死锁状态。
- 2、终止(或撤销)进程:终止或撤销系统中的一个或多个死锁进程,直到打破死锁状态为止。终止进程有以下两种方式:
(1)终止所有的死锁进程。这种方式简单粗暴。
(2)一个一个的终止进程,直到死锁状态解除为止。这个方法的代价也很大,因为每终止一个进程就需要使用死锁检测算法来检测系统当前是否处于死锁状态。且每次都应该采用最优策略来选择一个“代价最小”的进程来解除死锁状态。方法有( 进程的优先级、进程已运行时间以及运行完成还需要的时间、 进程已占用系统资源、进程运行完成还需要的资源、 终止进程数目、 进程是交互还是批处理等)。