哲學家就餐與死鎖問題,死鎖產生的條件以及解決方案
請結合經典案例-哲學家就餐,來談談你對死鎖的理解,以及怎麼預防和解除死鎖?
哲學家就餐
描述:在一張圓桌上,有n個哲學家,n支筷子,他們的生活方式只是交替地進行思考和進餐,飢餓時便試圖取其左、右最靠近他的筷子,只有在他拿到兩支筷子時才能進餐,進餐完畢,放下筷子又繼續思考。
根據描述,實現程式碼如下:
import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class Question17 { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); int sum = 5; Chopstick[] chopsticks = new Chopstick[sum]; for (int i = 0; i < sum; i++) { chopsticks[i] = new Chopstick(i); } for (int j = 0; j < sum; j++) { exec.execute(new Philosopher(chopsticks[j], chopsticks[(j + 1) % sum], j)); } } } // 筷子 class Chopstick { // 筷子位置 private int id; // 狀態 private boolean isUsed = false; public Chopstick(int id) { this.id = id; } // 拿取 public synchronized void take() throws InterruptedException { while (isUsed) { wait(); } System.err.println(this + " 被使用"); isUsed = true; } // 放下 public synchronized void drop() { isUsed = false; System.err.println(this + " 被放下"); notifyAll(); } @Override public String toString() { return "筷子[" + id + "]"; } } // 哲學家 class Philosopher implements Runnable { private Chopstick left; private Chopstick right; private int id; private Random rand = new Random(); public Philosopher(Chopstick left, Chopstick right, int id) { this.left = left; this.right = right; this.id = id; } @Override public void run() { while (!Thread.interrupted()) { try { think(); System.out.println(this + " 想吃飯!"); eat(); } catch (InterruptedException e) { System.err.println(this + " InterruptedException"); } } } // 思考 private void think() throws InterruptedException { System.out.println(this + " 思考..."); TimeUnit.MILLISECONDS.sleep(rand.nextInt(1) * 100); } // 吃飯 private void eat() throws InterruptedException { left.take(); right.take(); System.out.println(this + " 正在吃飯..."); TimeUnit.MILLISECONDS.sleep(rand.nextInt(2) * 100); left.drop(); right.drop(); } @Override public String toString() { return "哲學家[" + id + "]"; } }
通過執行結果,我們可以發現,到最後,沒有一個哲學家能過同時獲取兩隻筷子吃飯。
二、為什麼會產生死鎖
死鎖問題被認為是執行緒/程序間切換消耗系統性能的一種極端情況。在死鎖時,執行緒/程序間相互等待資源,而又不釋放自身的資源,導致無窮無盡的等待,其結果是任務永遠無法執行完成。哲學家問題便是執行緒資源競爭導致的死鎖現象,在程式執行一段時間後,程式所處的狀態是n位哲學家(n個執行緒)都各自獲取了一隻筷子的狀態,此時所有哲學家都想獲取第二隻筷子去吃飯,但是共享資源n只筷子已經都被n位哲學家握在手裡了,彼此想要的筷子都在其他哲學家手中,又沒有機制能讓任何哲學家放棄握在手中的筷子,從而照成了所有哲學家(所有執行緒)都在等待其他人手中資源的死鎖問題。
產生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個執行緒/程序使用。
- 請求與保持條件:一個執行緒/程序因請求資源而阻塞時,對已獲得的資源保持不放。
- 不剝奪條件:執行緒/程序已獲得的資源,在未使用完之前,不能強行剝奪。
- 迴圈等待條件:若干執行緒/程序之間形成一種頭尾相接的迴圈等待資源關係。
三、死鎖的解除與預防
一般解決死鎖的途徑分為死鎖的預防,避免,檢測與恢復這三種。
死鎖的預防是要求執行緒/程序申請資源時遵循某種協議,從而打破產生死鎖的四個必要條件中的一個或幾個,保證系統不會進入死鎖狀態。
死鎖的避免不限制執行緒/程序有關申請資源的命令,而是對執行緒/程序所發出的每一個申請資源命令加以動態地檢查,並根據檢查結果決定是否進行資源分配。
死鎖檢測與恢復是指系統設有專門的機構,當死鎖發生時,該機構能夠檢測到死鎖發生的位置和原因,並能通過外力破壞死鎖發生的必要條件,從而使得併發程序從死鎖狀態中恢復出來。
對於java程式來說,產生死鎖時,我們可以用jvisualvm/jstack來分析程式產生的死鎖原因,根治死鎖問題。