一、什么是死锁?
所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,一直保持死锁状态。
二、“哲学家就餐问题”
问题描述:
- 有五位沉默的哲学家围坐在一张圆桌旁,他们只会 思考 和 吃 ,思考的时候放下筷子,吃的话需要拿左右两边筷子。 如下图所示!
- 如果他们随便拿 ! 都想吃东西了,都拿自己左手边的话,这样每个人都吃不到,这种局面就是 死锁,一直死锁 会造成 饿死!!
- 那么现在有一个问题,我们需要想出一种方案,如何保证哲学家们可以交替吃饭和思考,而不会被饿死。
解决问题:
1. semaphore 设置信号量
- 我们可以发现 五个人同时吃就会造成死锁!
- 而四个人就不会,因为有一个人正在吃,这个人一旦吃完 其他阻塞等待的人就可以继续吃了。
- 所有假设有n个哲学家,我们通过设置一个信号初始值 n-1
2. 制定规则
我们也可让其左右都有筷子的时候才可以吃,也就是要求哲学家同时拿起俩筷子。
三、如何避免死锁
1. 产生死锁的四个条件
- 互斥条件:一个资源被一个线程占用,其他线程不能使用。(哲学家拿了一个筷子,别的哲学家不能拿)
- 不可抢占:在末使用完之前,不能强行剥夺。(一个哲学家正在吃,别的哲学家不能抢筷子)
- 请求与保持条件:请求别的资源的时候,自己的资源也不会自动释放。(哲学家请求拿右手筷子,但左手已经拿到的筷子仍拿着)
- 循环等待:存在一个等待队列(例子:每个哲学家都拿左手的筷子,等待右手的筷子)
2. 避免死锁的方式
- 互斥使用和不可抢占是 锁的特性,我们无法打破
- 请求和保持?大部分情况都无法改变!只有特定情况才能从这里入手
- 循环等待:最容易破坏
3. 通过 循环等待 解决死锁的方法
给线程加顺序
死锁的情况:
当t1线程获取到 lock1 想拿 lock2 ,t2 线程获取到 lock2 想拿 lock1。
这样就会产生循环等待,而造成死锁的情况。
public static void main(String[] args) { Object lock1 = new Object(); Object lock2 = new Object(); Thread t1 = new Thread(() -> { synchronized (lock1){ synchronized (lock2){ System.out.println(Thread.currentThread().getName()); } } },"t1"); Thread t2 = new Thread(()->{ synchronized (lock2){ synchronized (lock1){ System.out.println(Thread.currentThread().getName()); } } },"t2"); t1.start(); t2.start(); }
如何解决?
约定顺序加锁!比如下面的 t1 和 t2 线程都是先加 lock1 再加 lock2 的锁。
这样的话,就会避免循环等待。