Java哲學家進餐問題|多線程


Java實驗三 多線程

哲學家進餐問題

5個哲學家共用一張圓桌,分別坐在周圍的5張椅子上,

在圓桌上有5個碗和5只筷子(注意是5只筷子,不是5雙),

碗和筷子交替排列。他們的生活方式是交替地進行思考(thinking)和進餐(eating)。

平時,一個哲學家進行思考,飢餓時便試圖取用其左右最靠近他的兩只筷子,規定他必須先取左邊的筷子,再取右邊的筷子。
只有在他拿到兩只筷子時才能進餐。
進餐完畢,放下筷子繼續進行思考。

假如5位哲學家同時飢餓,各自拿起左邊的筷子時,再去拿各自右邊的筷子,因為無筷子可拿而陷入無期限等待(死鎖)。

進餐完畢釋放他用過的兩只筷子,從而使更多的哲學家能夠進餐。使用Java的多線程同步技術,實現上述解決方案。


解決方法一:破壞循環等待條件

最多允許4個哲學家同時去拿左邊的筷子,即死鎖四大必要條件之一——破壞循環等待條件。

synchronized關鍵字易混淆處:
用法:synchronized(一個對象){同步代碼塊}
這里synchronized鎖住的不是這"一個對象",而是用這"一個對象"來當看門人,只允許一個線程來進入synchronized鎖住的同步{代碼塊},所以這個代碼塊被稱作“同步代碼塊”,當代碼塊執行完后,會讓這“一個對象”(看門人),吼一聲讓其它線程進來。原理是:每一個java對象都關聯了一個“ monitor lock”,當一個線程獲取了 monitor lock 后,其它線程如果運行到獲取同一個 monitor 的時候就會被 block 住。當這個線程執行完同步代碼,則會釋放 monitor lock。

下面代碼,經過分析后能滿足最多允許4個哲學家同時去拿左邊的筷子的條件:

分析如下:

1.如果不加num<4,synchronized(left)后,如果另一個哲學家進程拿了right的筷子,另一個哲學家又拿了這個哲學家右邊的筷子...依此類推,產生循環等待鏈,即產生死鎖。就會產生死鎖。

我們標記3個結點去分析各個代碼塊進入的最多線程數。

2.加了num<4這個條件后,可能會有5個線程,同時滿足if判斷語句(結點1);而又因為synchronized了一個筷子(left),所以最多有4個進程進入了synchronized(left){}代碼塊鍾,執行了num++,(結點2),那么雖然此時可能有5個進程進入了if(num<4)語句,但是第5個進程也會被阻擋在synchronized外面。在synchronized(right)后,最多有2個進程去吃東西(結點3),因為只有5個筷子,同時只能讓兩個人去吃,其它3個人等待,看圖比較容易明白。


代碼如下:
class phiPerson implements Runnable{ //實現Runnable接口
	
	//全部使用static關鍵字 靜態成員屬於整個類,當系統第一次使用該類時,就會為其分配內存空間直到該類被卸載才會進行資源回收
	//為什么用static修飾 因為多線程多個哲學家需要共享筷子這個對象
	static Object[] chops;//static關鍵字修飾的變量 該類所有的對象共享同一個成員 
	static int Num = 0; //同時拿左手邊筷子的人數 也是全局變量
	private int pos; //當前哲學家的編號 私有變量
	
	public phiPerson(int position,Object[] chops) { //構造函數
		// TODO Auto-generated constructor stub
		this.chops =  chops;
		this.pos = position;
	}

	@Override
	public void run() { //重寫run方法
		// TODO Auto-generated method stub
		while(true) {
			int right = (pos+1)%5; //我右邊筷子在數組中的下標
			int left = (pos)%5;//左邊筷子在數組中的下標
			if(Num < 4) { //最多允許4個人同時拿左手邊的筷子
                           //結點1:5個進程都有可能進入這個地方
					synchronized (chops[left]) { //鎖 左手邊的筷子 就是等待左邊的人用我左手邊的筷子吃完了后我再拿來吃。。
                                               Num++;//同時拿左手邊筷子的人的數量+1 //這里沒有鎖住num,有可能會使得在沒有num++時,5個進程都進來,
                                                //結點2:最多4個進程進到這個地方,因為synchronized (chops[left])后,需要left筷子的另1個進程要在外面等待
						System.out.println(Num);
						System.out.println("第"+(pos+1)+"個哲學家拿了左手邊的筷子");
						synchronized(chops[right]) {//右手邊的筷子 鎖  就是等待右手邊的筷子沒人拿了我再拿
                                                        //結點3:最多有2個進程進入到這個地方,因為只有5個筷子,所以最多兩個人同時拿。
							System.out.println("第"+(pos+1)+"個哲學家拿了右手邊的筷子");
							System.out.println("第"+(pos+1)+"個哲學家正在eating");
							try {
								Thread.sleep(100);
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
							System.out.println("第"+(pos+1)+"個哲學家吃完了,把筷子放回了原處,開始thinking");
							Num--;//同時拿左手邊筷子的人的數量-1
						}
					}
				
			}
		}
	}
}

public class phiEat {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Object [] chObject = new Object[5];
		for(int i=0;i<5;i++) chObject[i] = i; //object必須要初始化一下呀
		
		//5個哲學家
		phiPerson firThread = new phiPerson(0,chObject);
		phiPerson secThread = new phiPerson(1,chObject);
		phiPerson thirThread = new phiPerson(2,chObject);
		phiPerson fourThread = new phiPerson(3,chObject);
		phiPerson fifThread = new phiPerson(4,chObject);
		//開吃了 
		new Thread(firThread).start();
		new Thread(secThread).start();
		new Thread(thirThread).start();
		new Thread(fourThread).start();
		new Thread(fifThread).start();
	}

}

解決方法二:破壞請求和保持條件

方案:僅當一個哲學家左右兩邊的叉子都可用時才允許他抓起叉子,即破壞死鎖四大條件之一——請求和保持條件

說明白點就是,不會出現某個哲學家拿一個筷子等一個筷子的情況,必須同時拿兩個!

package philosopher;

public class ThreadTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Chop fiveChops = new Chop();
		new Philosopher(0,fiveChops).start();
		new Philosopher(1,fiveChops).start();
		new Philosopher(2,fiveChops).start();
		new Philosopher(3,fiveChops).start();
		new Philosopher(4,fiveChops).start();
	}

}


class Philosopher extends Thread{
	private int index;
	private Chop chop;
	public Philosopher(int index, Chop chop) {
		// TODO Auto-generated constructor stub
		this.index = index;
		this.chop = chop;
	}
	
	@Override
	public void run() {
		while(true) {
			thinking();
			chop.takeChop(index);
			eating();
			chop.putChop(index);
		}
	}
	private void thinking(){
		// TODO Auto-generated method stub
		System.out.println("第"+index + "個哲學家正在思考...");
		try {
			sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void eating(){
		// TODO Auto-generated method stub
		System.out.println("第"+index + "個哲學家正在吃飯...");
		try {
			sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}


class Chop {
	private Boolean[] chops = {false,false,false,false,false};

	public synchronized void takeChop(int index) {
		// TODO Auto-generated method stub
		while(chops[index] || chops[(index+1)%5]) {
			try {
				wait(); //拿不到筷子就會被阻塞 進入等待池 從而不會再來競爭
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		chops[index] = true;
		chops[(index+1)%5] = true;
	}

	public synchronized void putChop(int index) {
		// TODO Auto-generated method stub
		chops[index] = false;
		chops[(index+1)%5] = false;
		notifyAll();
	}
	
}

這里產生疑問點待解決,notifyAll和wait在此代碼中如何工作。


參考文章:
[1] sunny_ss12 經典同步問題(二)---哲學家就餐問題
[2] Yun_Ge PV操作經典例題——哲學家進餐問題
[3] qiuhuilu JAVA多線程學習--哲學家就餐問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM