@Catyee
2021-01-13T08:24:20.000000Z
字数 2205
阅读 385
面试
死锁指两个进程/线程互相占有对方需要的资源,两个线程都向对方请求资源,同时自己还把持住资源不释放,就会导致两个线程始终处于互相等待的状态,就造成了死锁。
更严谨一点说,在一个系统中以下四个条件同时成立,那么就能引起死锁:
以上这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。所以解决死锁的思路也就是破坏死锁的四个必要条件中的一个或几个。
如果死锁已经发生,只能进行死锁的解除,解除死锁的关键在于释放一部分死锁进程占有的资源,让其他进程能够顺利执行,常见的做法有:
1、资源剥夺法:强行挂起或撤销某些进程,释放这些进程占有的资源
2、进程回退法:让某些进程回退到能够避免死锁的地步,释放被占有的资源,这种方法也增加了实现的难度和复杂度,要求保留执行的历史信息并设置还原点。
相比发生死锁之后接触死锁,更重要的还是死锁的预防和避免,常见的避免死锁的方式有:
1、限制加锁的顺序,也就是说不同进程按照相同的顺序加锁
2、设置锁的超时时间,给每个锁加一个超时时间,超过这个时间必须释放锁(基于数据库的分布式锁)
3、死锁检测,在即无法限制加锁顺序也不能限制锁的占有时间情况下,还有一种避免死锁的方式,就是进行死锁检测,系统对进程发出的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。(常用于关系性数据库的死锁避免方式)
线程1和线程2都占有对方的锁且不释放,同时又都申请对方占有的锁,导致死锁
public class DeadLock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
quietSleep(500);
synchronized (lock2) {
System.out.println("thread1 running");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
quietSleep(500);
synchronized (lock1) {
System.out.println("thread2 running");
}
}
});
thread1.start();
thread2.start();
}
private static void quietSleep(int interval) {
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
// ignore
}
}
}
有一张表test,只有两个字段:id和name,id是主键,name上没有索引
建表语句:
create table test(id int primary key, name varchar(20));
test表中初始有两条数据:
id | name |
---|---|
1 | a |
5 | c |
打开连个mysql的session,两个session都处于默认的可重复读隔离级别:
分别按照顺序执行以下语句:
序号 | session1 | session2 |
---|---|---|
1 | begin | |
2 | begin | |
3 | insert into test values(4,'b') | |
4 | update test set name='d' where name='c' | |
5 | insert into test values(3,'e') |
执行到第五步,会发现session2已经出现死锁的错误提示了:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
原因解释:
session1中在执行第三步的插入语句的时候会对id=3的这一行申请行锁(x锁),在事务未结束之前锁不会释放,在session2中执行第四步的时候,由于where条件中选择的是没有索引的name字段,所以会逐行加锁:id=1的行锁,2-3的间隙锁(gap锁),到了id=4这一行也会尝试加行锁,这个时候发现session1已经在这一行加了行锁,所以session2就会阻塞住;这个时候session1执行第五步,在id=3这一行进行插入,会尝试先在这一行加行锁,结果发现session2已经在这一行加了间隙锁,于是死锁出现了。当然mysql不会真的让死锁出现,mysql会自动撤销线程2的事务,并给出报错信息。