什么叫死锁?死锁案例?死锁必须满足哪些条件?如何定位死锁问题?有哪些解决死锁策略?哲学家问题?
1.死鎖是什么?
死鎖一定發生在并發環境中,死鎖是一種狀態,當兩個(或者多個線程)相互持有對方所需要的資源,卻又都不主動釋放手中持有的資源,導致大家都獲取不到自己想要的資源,所有相關的線程無法繼續執行。
2.死鎖案例
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;/*** @author weijie* @date 2020/4/28 10:43*/ public class DeadLockDemo {Object o1 = new Object();Object o2 = new Object();class Task1 implements Runnable{@Overridepublic void run() {synchronized (o1){System.out.println("task1 start ...");String threadName = Thread.currentThread().getName();System.out.print(threadName + "獲取i1對象鎖--->");/*** 阻塞當前線程*/try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){System.out.println("獲取i2對象鎖");}System.out.println("task2 end ...");}}}class Task2 implements Runnable{@Overridepublic void run() {synchronized (o2) {System.out.println("task2 start ...");String threadName = Thread.currentThread().getName();System.out.print(threadName + "獲取i2對象鎖--->");synchronized (o1) {System.out.println("獲取i1對象鎖");}System.out.println("task 2 end ...");}}}public static void main(String[] args) {DeadLockDemo deadLockDemo = new DeadLockDemo();ExecutorService executorService = Executors.newFixedThreadPool(2);executorService.submit(deadLockDemo.new Task1());executorService.submit(deadLockDemo.new Task2());}}3.死鎖必須滿足的四個條件?
4.如何用命令行和代碼定位死鎖?
1.通過命令行定位
在這里它首先會打印“Found one Java-level deadlock”,表明“找到了一個死鎖”。
然后是更詳細的信息,從中間這部分的信息中可以看出:
t2 線程想要去獲取這個尾號為 af0 的鎖對象,但是它被 t1 線程持有,同時 t2 持有尾號為 b00 的鎖對象; 相反,t1 想要獲取尾號為 b00 的鎖對象,但是它被 t2 線程持有,同時 t1 持有的卻是尾號為 af0 的鎖對象, 這就形成了一個依賴環路,發生了死鎖。 最后它還打印出了“Found 1 deadlock.”,
可以看出,jstack 工具不但幫我們找到了死鎖,甚至還把哪個線程、想要獲取哪個鎖、形成什么樣的環路都告訴我們了,當我們有了這樣的信息之后,死鎖就非常容易定位了,所以接下來我們就可以進一步修改代碼,來避免死鎖了。
2.代碼定位死鎖
ThreadMXBean用來定位死鎖問題
在main方法中添加如下代碼:
//保證發生死鎖Thread.sleep(3000);System.out.println("定位死鎖信息");ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();if(deadlockedThreads != null && deadlockedThreads.length > 0){for (int i= 0; i < deadlockedThreads.length; i++){long deadlockedThread = deadlockedThreads[i];ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThread);System.out.println("線程id為"+threadInfo.getThreadId()+",線程名為" + threadInfo.getThreadName()+"的線程已經發生死鎖,需要的鎖正被線程"+threadInfo.getLockOwnerName()+"持有。");}}
可以看出,ThreadMXBean 也可以幫我們找到并定位死鎖,如果我們在業務代碼中加入這樣的檢測,那我們就可以在發生死鎖的時候及時地定位,同時進行報警等其他處理,也就增強了我們程序的健壯性。
5.經典的哲學家問題
1.問題描述
哲學家就餐問題也被稱為刀叉問題,或者吃面問題。我們先來描述一下這個問題所要說明的事情,這個問題如下圖所示:
有 5 個哲學家,他們面前都有一雙筷子,即左手有一根筷子,右手有一根筷子。當然,這個問題有多個版本的描述,可以說是筷子,也可以說是一刀一叉,因為吃牛排的時候,需要刀和叉,缺一不可,也有說是用兩把叉子來吃意大利面。這里具體是刀叉還是筷子并不重要,重要的是必須要同時持有左右兩邊的兩個才行,也就是說,哲學家左手要拿到一根筷子,右手也要拿到一根筷子,在這種情況下哲學家才能吃飯。為了方便理解,我們選取和我國傳統最貼近的筷子來說明這個問題。
為什么選擇哲學家呢?因為哲學家的特點是喜歡思考,所以我們可以把哲學家一天的行為抽象為思考,然后吃飯,并且他們吃飯的時候要用一雙筷子,而不能只用一根筷子。
1. 主流程
我們來看一下哲學家就餐的主流程。哲學家如果想吃飯,他會先嘗試拿起左手的筷子,然后再嘗試拿起右手的筷子,如果某一根筷子被別人使用了,他就得等待他人用完,用完之后他人自然會把筷子放回原位,接著他把筷子拿起來就可以吃了(不考慮衛生問題)。這就是哲學家就餐的最主要流程。
2. 流程的偽代碼
我們來看一下這個流程的偽代碼,如下所示:
while(true) { // 思考人生、宇宙、萬物...think();// 思考后感到餓了,需要拿筷子開始吃飯pick_up_left_chopstick();pick_up_right_chopstick();eat();put_down_right_chopstick();put_down_left_chopstick();// 吃完飯后,繼續思考人生、宇宙、萬物... }while(true) 代表整個是一個無限循環。在每個循環中,哲學家首先會開始思考,思考一段時間之后(這個時間長度可以是隨機的),他感到餓了,就準備開始吃飯。在吃飯之前必須先拿到左手的筷子,再拿到右手的筷子,然后才開始吃飯;吃完之后,先放回右手的筷子,再放回左手的筷子;由于這是個 while 循環,所以他就會繼續思考人生,開啟下一個循環。這就是整個過程。
3.有死鎖和資源耗盡的風險
這里存在什么風險呢?就是發生死鎖的風險。如下面的動畫所示:
根據我們的邏輯規定,在拿起左手邊的筷子之后,下一步是去拿右手的筷子。大部分情況下,右邊的哲學家正在思考,所以當前哲學家的右手邊的筷子是空閑的,或者如果右邊的哲學家正在吃飯,那么當前的哲學家就等右邊的哲學家吃完飯并釋放筷子,于是當前哲學家就能拿到了他右手邊的筷子了。
但是,如果每個哲學家都同時拿起左手的筷子,那么就形成了環形依賴,在這種特殊的情況下,每個人都拿著左手的筷子,都缺少右手的筷子,那么就沒有人可以開始吃飯了,自然也就沒有人會放下手中的筷子。這就陷入了死鎖,形成了一個相互等待的情況。
4.代碼演示
代碼如下所示:
package com.netty.rpc.test.util;import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean;public class DiningPhilosophers {public static class Philosopher implements Runnable {private Object leftChopstick;private Object rightChopstick;public Philosopher(Object leftChopstick, Object rightChopstick) {this.leftChopstick = leftChopstick;this.rightChopstick = rightChopstick;}@Overridepublic void run() {try {while (true) {doAction("思考人生、宇宙、萬物、靈魂...");synchronized (leftChopstick) {doAction("拿起左邊的筷子");synchronized (rightChopstick) {doAction("拿起右邊的筷子");doAction("吃飯");doAction("放下右邊的筷子");}doAction("放下左邊的筷子");}}} catch (InterruptedException e) {e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + " " + action);Thread.sleep((long) (Math.random() * 10));}}public static void main(String[] args) throws InterruptedException {Philosopher[] philosophers = new Philosopher[5];Object[] chopsticks = new Object[philosophers.length];for (int i = 0; i < chopsticks.length; i++) {chopsticks[i] = new Object();}for (int i = 0; i < philosophers.length; i++) {Object leftChopstick = chopsticks[i];Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];philosophers[i] = new Philosopher(rightChopstick, leftChopstick);new Thread(philosophers[i], "哲學家" + (i + 1) + "號").start();}Thread.sleep(1000 * 10);ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();if(deadlockedThreads != null && deadlockedThreads.length > 0){for (int i= 0; i < deadlockedThreads.length; i++){long deadlockedThread = deadlockedThreads[i];ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThread);System.out.println("線程id為"+threadInfo.getThreadId()+",線程名為" + threadInfo.getThreadName()+"的線程已經發生死鎖,需要的鎖正被線程"+threadInfo.getLockOwnerName()+"持有。");}}} }運行后截圖:
哲學家 1、3、2、4、5 幾乎同時開始思考,然后,假設他們思考的時間比較相近,于是他們都在幾乎同一時刻想開始吃飯,都紛紛拿起左手的筷子,這時就陷入了死鎖狀態,沒有人可以拿到右手的筷子,也就沒有人可以吃飯,于是陷入了無窮等待,這就是經典的哲學家就餐問題。
5.多種解決方案
對于這個問題我們該如何解決呢?有多種解決方案,這里我們講講其中的幾種。前面我們講過,要想解決死鎖問題,只要破壞死鎖四個必要條件的任何一個都可以。
1. 服務員檢查
第一個解決方案就是引入服務員檢查機制。比如我們引入一個服務員,當每次哲學家要吃飯時,他需要先詢問服務員:我現在能否去拿筷子吃飯?此時,服務員先判斷他拿筷子有沒有發生死鎖的可能,假如有的話,服務員會說:現在不允許你吃飯。這是一種解決方案。
2. 領導調節
我們根據上一講的死鎖檢測和恢復策略,可以引入一個領導,這個領導進行定期巡視。如果他發現已經發生死鎖了,就會剝奪某一個哲學家的筷子,讓他放下。這樣一來,由于這個人的犧牲,其他的哲學家就都可以吃飯了。這也是一種解決方案。
3. 改變一個哲學家拿筷子的順序
我們還可以利用死鎖避免策略,那就是從邏輯上去避免死鎖的發生,比如改變其中一個哲學家拿筷子的順序。我們可以讓 4 個哲學家都先拿左邊的筷子再拿右邊的筷子,但是有一名哲學家與他們相反,他是先拿右邊的再拿左邊的,這樣一來就不會出現循環等待同一邊筷子的情況,也就不會發生死鎖了。
死鎖解決
我們把“改變一個哲學家拿筷子的順序”這件事情用代碼來寫一下,修改后的 main 方法如下:
if (i == philosophers.length - 1) ,在這種情況下,我們給它傳入的筷子順序恰好相反,這樣一來,他拿筷子的順序也就相反了,他會先拿起右邊的筷子,再拿起左邊的筷子。那么這個程序運行的結果,是所有哲學家都可以正常地去進行思考和就餐了,并且不會發生死鎖。
總結
以上是生活随笔為你收集整理的什么叫死锁?死锁案例?死锁必须满足哪些条件?如何定位死锁问题?有哪些解决死锁策略?哲学家问题?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JedisConnectionExcep
- 下一篇: 关于kafka中acks是否可以为all