死锁
死锁: 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
一个线程加上锁之后,解不开了,僵住了…
死锁的几种情况
1️⃣一个线程一把锁,线程连续加锁两次~
如果这个锁是不可重入锁,就是死锁!
synchronized
是可重入锁,没这个问题~
2️⃣两个线程,两把锁~
- 钥匙锁车里了,车钥匙锁家里了
- 一码通挂了,程序员回家需要出示一码通,但是要想出示一码通需要先回家修bug
这种死锁,可重入锁解决不了!
死锁代码:
public class TestDemo1 {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1获取locker1成功!");
System.out.println("t1尝试获取locker2");
synchronized (locker2) {
System.out.println("t1获取两把锁成功!");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (locker2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2获取locker2成功!");
System.out.println("t2尝试获取locker1");
synchronized (locker1) {
System.out.println("t2获取两把锁成功!");
}
}
});
thread1.start();
thread2.start();
}
}
3️⃣ 多个线程多把锁
这个情况更容易死锁!!
描述这个死锁场景,有一个典型的模型,哲学家就餐问题~
这里有一个桌子,桌子上放着一碗意大利面,桌边坐着五个哲学家,一共只有五根筷子,分别放到两个人之间~
每个哲学家只会做两件事:
- 思考人生,啥都不干(线程阻塞了)
- 吃面条,先拿起左手的筷子,再拿起右手的筷子,然后吃面,吃饱了就放下了~
在这个过程中,什么时候吃面条,什么时候思考人生,是不确定的(线程随机调度)
大部分情况下,这个模型是可以良好运转的 ,不会死锁~
但是极端情况下,就会出现死锁!!
假设:
五个哲学家同时拿起左手的筷子!
这个时候再尝试拿右手的筷子时,就发现没有筷子可拿(拿不起来)
并且这几个哲学家互不相让,此时就会陷入僵局,谁都没办法完成吃面条这个操作~
如何解决死锁问题
明确了死锁问题,如何解决?
死锁的四个必要条件:
- 互斥使用,锁A 被线程1占用,线程2就用不了~
- 不可抢占,锁A被线程2占用,线程2不能把锁A给抢过来,除非线程1 主动释放
- 请求和保持,有多把锁,线程1拿到锁A之后,不想释放锁A,还想拿到一个锁B
- 循环等待,线程1等待线程2释放锁, 线程2要释放锁得等待线程3来释放锁,线程3释放锁还得等待线程1释放锁~
必要条件,只要打破其中一个,问题就迎刃而解了❗
1,2打破不了,是锁的基本特性~
想打破3取决于代码的写法,看看获取锁B的时候是否先释放锁A了? 有可能打破,但是还是要看具体的需求是否允许这么写~这里这个方案可能能行,但是不普适 ~
那只剩第4点了,而第4点也有把握打破!
只需要约定好加锁的顺序,就可以避免循环等待~ |
例如:
给锁编号
约定,加多个锁的时候,必须先加编号小的锁,后加编号大的锁,就可以有效地避免循环等待了~
比如把上面的代码按照上述约定修改一下:
public class TestDemo1 {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread thread1 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1获取locker1成功!");
System.out.println("t1尝试获取locker2");
synchronized (locker2) {
System.out.println("t1获取两把锁成功!");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (locker1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2获取locker1成功!");
System.out.println("t2尝试获取locker2");
synchronized (locker2) {
System.out.println("t2获取两把锁成功!");
}
}
});
thread1.start();
thread2.start();
}
}
运行结果:
图解: