用交通状况做比喻,死锁 = 十字路口交叉相行的车把对方的路给堵死了,造成大堵车情况。
1. 死锁产生及其检测算法
要发生死锁还得有以下条件:
- 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
- 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
2. 死锁的解决方式
《并发编程的艺术》中给出的解决方法:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
以上四种其实是预防方法,如果是已发生死锁,可以有以下几种恢复方式: - 利用抢占恢复
- 利用回滚恢复
- 通过杀死进程恢复
3. DeadLock的代码演示
本篇文章中演示死锁的场景,是在一些更为 复杂的场景中可能会遇到的问题。比如t1拿到锁之后,因为一些异常情况没有释放锁 (死循环)。又或者是t1拿到一个数据库锁,释放锁的时候抛出了异常,没释放掉。
一旦出现死锁,业务是可感知的,因为不能继续提供服务了,那么只能通过dump线程查看 到底是哪个线程出现了问题,以下线程信息告诉我们是DeadLockDemo类的第42行和第31行引 起的死锁
1.创建t1和t2两个线程,互相加锁
public class NormalLock {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
//给原始类型int的Class对象方法加锁
synchronized (Integer.class) {
System.out.println("t1 Integer");
synchronized (Long.class) {
System.out.println("t1 Long");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (Long.class) {
System.out.println("t2 Long");
synchronized (Integer.class) {
System.out.println("t2 Integer");
}
}
});
t2.start();
}
}
结果:
2,给t1一定的线程时间
public class DeadLock {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (Integer.class) {
System.out.println("t1 Integer");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Long.class) {
System.out.println("t1 Long");
}
}
});
t1.start();
Thread t2 = new Thread(() -> {
synchronized (Long.class) {
System.out.println("t2 Long");
synchronized (Integer.class) {
System.out.println("t2 Integer");
}
}
});
t2.start();
}
}
结果就是一直卡住:
可以看出,要避免一个线程同时获取多个锁。
- 就是避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。 ·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
参考文章:
github—计算机系统之死锁