|
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。简单来说就是: 死锁, 一个线程加上锁之后, 解不开了, 僵住了.
场景1: 一个线程, 一把锁,
线程连续加锁两次, 如果这个锁是不可重入锁, 就会变成死锁. (synchronized
是可重入锁, 没有这个问题)
场景2: 两个线程两把锁
比如: 家里钥匙锁车里了, 车钥匙锁家里了.
public class Test {
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
System.out.println("t1 尝试获取 locker1");
synchronized (locker1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 尝试获取 locker2");
synchronized (locker2) {
System.out.println("t1 获取两把锁成功!");
}
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2 尝试获取 locker2");
synchronized (locker2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 尝试获取 locker1");
synchronized (locker1) {
System.out.println("t2 获取两把锁成功!");
}
}
});
t1.start();
t2.start();
}
}
运行结果展示, 无法再获取到锁.
场景3: 多个线程多把锁 (更容易死锁)
哲学家就餐问题
有五个哲学家, 五根筷子. 五根筷子分别放到每两个哲学家之间.
每个哲学家只会做两件事:
- 思考, 什么都不做(线程阻塞了)
- 吃面条, 先拿起左手的筷子, 再拿起右手的筷子, 吃面条, 吃完放下筷子.
大部分情况, 这个模型是可以良好运行的, 不会发生死锁.
但如果五个哲学家, 在同一时间, 同时
拿起左手的筷子. 再拿右手的筷子时, 发现右手边没有筷子. 并且这几个哲学家互不相让, 此时就会陷入僵局.
死锁产生的四个必要条件
:
所以我们只要打破以上四种的其中一个, 就可以避免死锁
- 互斥使用, 不可打破, 是锁的基本特性
- 不可抢占, 不可打破, 是锁的基本特性
- 请求和保持, 有可能打破, 取决于代码的写法, 看获取锁B 的时候是不是先释放锁A. (主要看需求场景是否允许这么写)
- 循环等待, 有把握打破! 只要约定好加锁的顺序, 就可以打破循环等待.
比如: 给锁编号, 约定, 加多个锁的时候, 必须先加编号小的锁, 后加编号大的锁. 就可以有效避免循环等待.
我们给筷子进行编号
, 先拿编号小的, 后拿编号大的. (不再是先拿左手再拿右手)
假设这时五个哲学家又同时
伸手拿第一支筷子(尝试获取第一把锁)
继续向下, 直到👸吃完, 👼开始吃面.
还有一个解决方案, 为 “银行家算法”, 就是把所有的资源统一进行统筹分配, 也能避免死锁. 但这个方法最大的问题就是比较复杂, 不太适合实际开发.
|
以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!