1. 什么是死锁
死锁(Deadlock)是多线程编程中的一个常见问题,指的是两个或多个线程相互等待对方释放资源,导致程序无法继续执行的状态。
在一种典型的死锁情况中,有两个或多个线程,每个线程都在持有一个资源的同时试图获得另一个线程持有的资源。当两个线程都在等待对方释放资源时,它们将永远无法继续执行,产生了死锁。
代码示例:
public class Demo26 { 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(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (locker2) { System.out.println("t1获取locker2"); } } }); Thread t2 = new Thread(() ->{ System.out.println("t2获取locker2"); synchronized (locker2) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } synchronized (locker1) { System.out.println("t2获取locker1"); } } }); t1.start(); t2.start(); } }
运行结果:
解释: t1对locker1加锁,t2对locker2加锁. 然后t1要获取locker2,t2要获取locker1. 但是t1和t2并没有执行完代码,并不会释放锁,需要获取对方的锁之后,才会释放锁. 因此它们就"僵"住了. 代码执行不下去了.
之前看到过一句话也能很好的理解这个问题,比如你去面试,面试官问你一个问题. 你回答说"你先给我发offer,我就回答问题". 面试官说"你先回答问题,我再给你发offer".
2. 哲学家吃饭问题
死锁还有一个很经典的问题,就是哲学家吃饭问题.
哲学家吃饭问题: 有五位哲学家围坐在一张圆形餐桌周围,每个哲学家需要交替进行思考和进餐。在餐桌上有五只筷子,每个哲学家的左右两边分别放有一只筷子。哲学家只能同时使用自己左右两边的筷子来进餐,而当一个哲学家使用筷子时,其他哲学家必须等待。
如图所示:
上述可能出现死锁的原因是: 两个及以上的哲学家可能会去争夺同一根筷子.
极端情况就是五个哲学家同时拿起自己左手边的一根筷子,想去拿右边的筷子,却发现已经被拿了. 谁也吃不到饭,无法释放筷子(锁).
解决上述问题的关键就是 如何解决死锁问题.
3.如何解决死锁
死锁的四个必要条件:
1.互斥使用: 资源不能同时被多个线程持有,只能被一个线程独占。
2.不可抢占: 一个线程获得资源后,不能被其他线程强制性地抢占。
3.请求和保持: 一个线程持有一个资源的同时,又请求另一个线程持有的资源。
4.循环等待: 若干个线程之间形成一种循环等待资源的关系。
避免死锁,只需要让其中任意一个条件不满足即可.
其中1和2是锁的基本特性,无法改变. 因此只能从条件3和条件4方面下手.
而条件3虽然可能能行,但是可能会带来新的问题.因此需要从条件4上打破
可以给上述的筷子设置编号. 设置好加锁的循序,每次先给编号小的进行加锁就可以解决上述问题.
以哲学家吃饭的极端情况为例:
默认情况下,一个人一根筷子.
对加锁的顺序进行设置(先拿序号小的).
这里的5号哲学家不会拿筷子.因为对于5号哲学家来说,5比1大,5号哲学家要拿1号筷子. 但1号筷子已经被拿了,所以5号哲学家就进入"阻塞等待"了. 那么4号玩家就可以继续拿起5号筷子进行吃饭,然后进行释放筷子(锁),接着是3号,2号,1号.等1号哲学家释放完1号筷子,那么此时5号哲学家就可以拿起1号筷子和5号筷子进行吃饭了
感谢你的观看!希望这篇文章能帮到你!
Java专栏在不断更新中,欢迎订阅!
“愿与君共勉,携手共进!”