死锁及哲学家问题——解决方案

发布于:2023-01-14 ⋅ 阅读:(479) ⋅ 点赞:(0)

死锁

简单来说就是 : 线程获取了锁, 由于某些原因没有及时释放, 导致其他线程无法获取到, 且没法继续往下执行, 一直处于这种状态。

严谨来说就是: 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。(来自百科)

产生死锁的情况

产生死锁通常有三种情况

1.只有一把锁

如果这把锁是一个不可重入锁, 而又进行多次加锁, 例如下面这种情况 (代码仅供理解)

public class Tmp {
    public static void main(String[] args) {
        Lock lockA = new Lock(); // 假设这个是不可重入锁
        lockA.lock();-----------------------------|
        lockA.lock();--------------|              |              
        ...                    第二层锁          第一层锁 
        lockA.unLock();------------|              | 
        lockA.unLock();---------------------------|
    }
}

我们记这个不可重入锁为 锁A, 如上, 某线程执行上面代码, 获取完第一次锁后, 开始尝试获取第二层锁, 但是 锁A 已经被该线程获取了, 所以第二层锁无法获取, 就会进入阻塞, 等待线程释放 锁A, 而 锁A还在它身上, 其他线程也无法获取这个 锁A, 也没法主动释放锁。就陷入了死锁

2.两个锁的情况

比如这种情况, 两个锁都已经拥有自己的锁了, 但是它们又都想去获取彼此的锁, 都在等对方释放, 但是两人都不主动释放

在这里插入图片描述

3.多个锁的情况

这个最经典的就是哲学家问题了

问题情形: 有张桌子上有面条和5根筷子(锁), 拥有两根筷子就可以吃面, 桌子周围还有五位哲学家(线程), 要干的事就是思考人生(线程阻塞 / 休眠) 和 吃 面条(运行), 哲学家只能拿自己左右两边的筷子

在这里插入图片描述

上述情形, 可以认为一根筷子就是一根锁, 而需要拿两根筷子(锁) 才能吃面条(在 CPU 上工作)

如果在某一时刻, 所有哲学家都想刚好想吃面, 如果所有哲学家都同时拿起了自己左手边的筷子

在这里插入图片描述

而当哲学家想拿另一个筷子的时候, 就发现没有一个哲学家能吃到面, 如果他们非常固执, 就是不放筷子, 那么五个哲学家都没法吃到面, 也就是说五个线程都没法正常工作, 也就陷入了死锁问题。

解决方案

在这种场景下, 如果要避免死锁问题的发生, 我们可以指定一个规则, 并让所有线程共同遵守

给所有的锁进行编号, 规定只能按照固定的顺序获取锁

还是上面的场景, 我们如果按照这个规则, 对所有筷子(锁) 进行编号
在这里插入图片描述

然后规定: 所有哲学家在选择可以获取的锁时, 必须选择编号更小的锁, 那么我们先让五个哲学家都选择一个筷子(锁)

A哲学家 在1, 2号筷子中选, 只能选小的, 那就是1号筷子, 然后 B哲学家 在2, 3筷子中选…一直到 E哲学家, 只能在1, 5号筷子中选, 只能选小的, 即1号筷子, 但是1号筷子已经被占用了, 那就只能阻塞等待, E哲学家一个筷子都没法拿

在这里插入图片描述
但是A, B, C哲学家也没法拿另一个筷子, 也没法吃面。唯有 D 哲学家可以拿到 5 号筷子, 然后吃面, 吃碗面, 放下筷子, 给其他哲学家吃, 这样哲学家就都能吃到面条了。

可以发现: 上面几个发生死锁的场景都有一个共同点 → 循环等待, 线程1 等待 线程 2, 线程2 又在等待其他线程…这也是发生死锁的必要条件.

总结一下, 我们可以制定一个规则, 让所有线程都去遵守——当线程可以选择获取多个锁的时候, 获取锁的顺序需要是从小到大来获取。如果小的锁还没法获取, 那就进入阻塞

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到