死锁 以及死锁的解决策略
死锁
我们需要知道的是,死锁是如何产生的,以及如何相关的应对策略。
1、什么叫死锁?
首先,我们要明确,线程和线程,进程和进程之间都是可以发生死锁的。
进程间的死锁:通俗的来说,就是几个进程由于资源调度不合理,导致两个甚至更多的进程出现了无法向下执行的情况,那么我们就称之为死锁。
线程间的死锁:我们假设有两个线程A和B,线程A持有锁A,线程B持有锁B,此时线程A想要去获取B的锁才能继续向下执行,线程B也想获取A的锁然后向下执行,但是两个线程的锁都在自己手里,别人无法获得,那么两个线程就会陷入一个比较尴尬的境地,无法获取锁也无法向下执行,那么我们就把这种情况叫做线程的死锁。如下图所示:
2、死锁的条件:
我们这里一般使用进程死锁的条件为例:
-
互斥条件
就是指某一个资源在某一时刻只能由一个进程获取,其他进程只能等待该进程释放资源以后才能获取。
-
请求和保持条件
就是指一个进程获取了自己想要的部分资源,但是想要的其他资源还未分配给自己,此时自己无法往下执行,但是也不会释放已经获取到的资源,那么该进程获取到的资源就相当于进入了阻塞状态。
-
不可剥夺条件
在一个资源被进程获取以后,如果进程没有使用完这个资源进行释放,那么其他进程是不能进行抢占使用的。
-
循环等待条件
满足上述三个条件以后,就会进入循环等待条件。
3、死锁的应对策略:
3.1 死锁的预防
死锁的预防其实就是打破死锁的条件中至少一个,那么死锁就会不攻自破。
资源的一次性分配,其实就是将系统资源提前进行合理的分配,那么就不会产生那么多的问题了。
资源的有效分配,也就是资源的序号化,其实就是给各个资源一个执行顺序,到了给定的时间就去找自己应该效忠的主人一样,那么自然不会存在资源的抢夺了。
3.2死锁的避免
使用银行家算法。所谓的银行家算法,其实就是提前将系统中各个进程执行需要的资源做一个统计,这个统计会将资源合理的分配给当前资源可以支持完成运行的进程,如果一些进程的资源需求量超过了当前系统资源的剩余量,那么只能等待其他进程将此资源释放以后,再次进行统计,直到系统资源剩余量满足进程执行那么才会分配资源进行执行。
3.3死锁的处理
剥夺资源,甚至直接杀死进程。
4、Java中死锁的实现(摘自大佬的内容)
具体可见https://blog.csdn.net/ZHWang102107/article/details/83042406(感谢感谢!!!)
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
public void run() {
synchronized (a) {
try {
System.out.println("now i in threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now i in threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (b) {
try {
System.out.println("now i in threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now i in threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
}
});
threadA.start();
threadB.start();
}
为了解决这个问题,我们不使用显示的去锁,我们用信号量去控制。
信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。
对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class UnLockTest {
public static String obj1 = "obj1";
public static final Semaphore a1 = new Semaphore(1);
public static String obj2 = "obj2";
public static final Semaphore a2 = new Semaphore(1);
public static void main(String[] args) {
LockAa la = new LockAa();
new Thread(la).start();
LockBb lb = new LockBb();
new Thread(lb).start();
}
}
class LockAa implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockA 开始执行");
while (true) {
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockA 锁住 obj1");
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() + "LockA 锁 obj2 失败");
}
}else{
System.out.println(new Date().toString() + "LockA 锁 obj1 失败");
}
UnLockTest.a1.release(); // 释放
UnLockTest.a2.release();
Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockBb implements Runnable {
public void run() {
try {
System.out.println(new Date().toString() + " LockB 开始执行");
while (true) {
if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockB 锁住 obj2");
if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
System.out.println(new Date().toString() + " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // do something
}else{
System.out.println(new Date().toString() + "LockB 锁 obj1 失败");
}
}else{
System.out.println(new Date().toString() + "LockB 锁 obj2 失败");
}
UnLockTest.a1.release(); // 释放
UnLockTest.a2.release();
Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
如何避免死锁
1、我们可以使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间
2、信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。
对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出
上一篇: 【Mysql】生产死锁临时解决
下一篇: MySQL死锁解决