1. 程式人生 > >哲學家就餐與死鎖問題,死鎖產生的條件以及解決方案

哲學家就餐與死鎖問題,死鎖產生的條件以及解決方案

請結合經典案例-哲學家就餐,來談談你對死鎖的理解,以及怎麼預防和解除死鎖?

哲學家就餐

描述:在一張圓桌上,有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位哲學家握在手裡了,彼此想要的筷子都在其他哲學家手中,又沒有機制能讓任何哲學家放棄握在手中的筷子,從而照成了所有哲學家(所有執行緒)都在等待其他人手中資源的死鎖問題。

產生死鎖的四個必要條件: 

  1. 互斥條件:一個資源每次只能被一個執行緒/程序使用。
  2. 請求與保持條件:一個執行緒/程序因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:執行緒/程序已獲得的資源,在未使用完之前,不能強行剝奪。
  4. 迴圈等待條件:若干執行緒/程序之間形成一種頭尾相接的迴圈等待資源關係。

三、死鎖的解除與預防

一般解決死鎖的途徑分為死鎖的預防,避免,檢測與恢復這三種。

死鎖的預防是要求執行緒/程序申請資源時遵循某種協議,從而打破產生死鎖的四個必要條件中的一個或幾個,保證系統不會進入死鎖狀態。

死鎖的避免不限制執行緒/程序有關申請資源的命令,而是對執行緒/程序所發出的每一個申請資源命令加以動態地檢查,並根據檢查結果決定是否進行資源分配。

死鎖檢測與恢復是指系統設有專門的機構,當死鎖發生時,該機構能夠檢測到死鎖發生的位置和原因,並能通過外力破壞死鎖發生的必要條件,從而使得併發程序從死鎖狀態中恢復出來。

對於java程式來說,產生死鎖時,我們可以用jvisualvm/jstack來分析程式產生的死鎖原因,根治死鎖問題。