1. 程式人生 > >java多執行緒--哲學家就餐問題

java多執行緒--哲學家就餐問題

在使用java中的sychronized或者顯示鎖來進行互斥操作時,就可能會出現死鎖的情況:即任務一獲得A資源,等待B資源。任務二獲得B資源等待C資源。任務三獲得C資源等待D資源。而任務四獲得D資源等待A資源,這樣就造成一個連續的迴圈等待,沒有哪個執行緒能夠繼續下去,稱為死鎖。
對於死鎖,哲學家就餐問題就是一個經典的例子。

問題描述[1]:
在一個圓桌上,有n個哲學家,n只筷子,每個哲學家左右兩邊各返一隻筷子。哲學家可以進行思考和吃飯,思考時,不獲取筷子。吃飯時,必須同時獲得左右兩隻筷子才能吃飯(先獲得右邊,再獲得左邊)。

先來看看chopstick類:

package philosopher;
/**
 * 滿足:
 * 每根筷子同時只能被一個哲學家獲得,若有另外一個哲學家請求獲得該筷子,則需要等待
 * 哲學家使用完筷子之後就放回並通知其他哲學家使用
 * @author
lecky * */
public class Chopstick { private int index; private boolean use = false; public Chopstick(int index) { super(); this.index = index; } @Override public String toString() { return "Chopstick [index=" + index + "]"; } /* * 獲得筷子 * 該筷子被獲得之後,當有其他哲學家執行緒來請求獲得時,都需要等待 */
public synchronized void take() throws InterruptedException{ while(use) wait(); use =true; } /* * 歸還筷子 * 當持有該筷子的哲學家使用完畢之後,就將其歸還,並通知其他在等待該筷子資源的哲學家 */ public synchronized void drop(){ use = false; notifyAll(); } }

Philosopher類:
哲學家先獲得右邊的筷子,再獲得左邊的筷子。在獲得左邊的筷子時,若左邊的筷子已經被相鄰哲學家獲得,則需要等待直到其釋放該資源為止,即left.take()會阻塞,直到被通知。

package philosopher;

import java.util.Random;

/**
 * 每個哲學家可以進行思考或者吃飯,吃飯時需要先後獲得右邊和左邊的筷子
 * 若沒有同時獲得右邊和左邊的筷子,則等待,
 * 若使用完之後就返回。
 * @author lecky
 *
 */
public class Philosopher implements Runnable{
    private Chopstick right ; 
    private Chopstick left;
    private int index;
    private int thinkTime;
    public Philosopher(Chopstick right, Chopstick left, int index, int thinkingTime) {
        super();
        this.right = right;
        this.left = left;
        this.index = index;
        this.thinkTime = thinkingTime;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                System.out.println(this + " thinking .......");
                thinking();
                System.out.println(this+" start to eat and take right stick");
                right.take();
                System.out.println(this+" take left stick");
                left.take();
                System.out.println(this+" eating");
                thinking();//吃飯
                right.drop();
                left.drop();
            }
        } catch (InterruptedException e) {
            System.out.println(this+"InterruptedException");
        }

    }

    /**
     * 哲學家思考時間,由thinkingTime因子決定
     * @throws InterruptedException
     */
    private void thinking() throws InterruptedException{
        Thread.sleep(thinkTime*100);
    }

    @Override
    public String toString() {
        return "Philosopher [index=" + index + "]";
    }
}

產生死鎖的版本:
測試類中有5個哲學家和5只筷子,將思考時間定為0,就會很快出現死鎖的情況。

package philosopher;


import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

/**
 * 會產生死鎖的版本
 * 5個哲學家,5只筷子,每個哲學家吃飯之前需要先拿到右邊的筷子,然後再拿到左邊的筷子
 * 之後才能吃飯
 * @author lecky
 *
 */
public class DeadlockPhilosopher {

    @Test
    public void test() throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        int size=5;
        int thinkingTime=0;
        Chopstick[] chopstick = new Chopstick[size];
        for(int i=0;i<size;i++)
            chopstick[i] = new Chopstick(i);
        for(int i=0;i<size;i++)
            executor.execute(new Philosopher(chopstick[i], chopstick[(i+1)%size], i, thinkingTime));
        Thread.sleep(4*1000);
        executor.shutdownNow();
    }

}

執行結果:
分析執行結果會發現,
Philosopher [index=0]獲得right stick[0],請求left stick
Philosopher [index=1]獲得right stick[1],請求left stick
Philosopher [index=2]獲得right stick[2],請求left stick
Philosopher [index=3]獲得right stick[3],請求left stick
Philosopher [index=4]獲得right stick[4],請求left stick
而沒有一個能夠同時獲得兩隻筷子吃飯的哲學家。

Philosopher [index=0] thinking .......
Philosopher [index=3] thinking .......
Philosopher [index=4] thinking .......
Philosopher [index=1] thinking .......
Philosopher [index=4] start to eat and take right stick
Philosopher [index=3] start to eat and take right stick
Philosopher [index=2] thinking .......
Philosopher [index=0] start to eat and take right stick
Philosopher [index=2] start to eat and take right stick
Philosopher [index=3] take left stick
Philosopher [index=4] take left stick
Philosopher [index=1] start to eat and take right stick
Philosopher [index=2] take left stick
Philosopher [index=0] take left stick
Philosopher [index=1] take left stick
Philosopher [index=1]InterruptedException
Philosopher [index=0]InterruptedException
Philosopher [index=2]InterruptedException
Philosopher [index=3]InterruptedException
Philosopher [index=4]InterruptedException

產生死鎖的條件:(簡要)

  1. 互斥條件。至少有一個資源(筷子)只能同時被一個任務獲得。
  2. 迴圈等待。這裡每個哲學家都按照先獲得右邊的筷子,然後再獲得左邊的筷子的方式進行,那麼就會構成一個迴圈等待。

修改後的測試類:
破壞第二個產生死鎖的條件:迴圈等待。
將第五個哲學家就餐拿起筷子的順序更改,不使之構成迴圈條件。

package philosopher;

import static org.junit.Assert.*;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;
/**
 * 破壞產生死鎖的迴圈條件
 * 使第五個哲學家不按照先獲得右邊筷子,再獲得左邊筷子的方式進行
 * @author lecky
 *
 */
public class FixedPhilosopher {

    @Test
    public void test() throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        int size=5;
        int thinkingTime = 0;
        Chopstick[] chopsticks = new Chopstick[size];
        for(int i=0;i<size;i++)
            chopsticks[i]=new Chopstick(i);
        for(int i=0;i<size-1;i++)
            executor.execute(new Philosopher(chopsticks[i], chopsticks[i+1], i, thinkingTime));
        executor.execute(new Philosopher(chopsticks[0], chopsticks[size-1], size, thinkingTime));//更改第五個哲學家獲得筷子的順序
        Thread.sleep(100*1000);
        executor.shutdownNow();
    }

}

[1].thinking in java