死锁与哲学家吃饭问题

一、死锁情景

有5个哲学家,围坐在一张圆桌子上,桌子上放着5根筷子,分别位于两个哲学家之间,如图所示:哲学家吃饭
他们有时会吃饭,有时会思考。吃饭时,哲学家会先拿起左手的那根筷子,再去拿右手的那根。当一个哲学家拿到两根筷子时,就可以开始吃饭,吃着吃着,又会放下筷子开始思考。

以上描述的场景,就会出现死锁:在某一时刻,所有的哲学家同时拿起左手边的筷子,再去拿右边的筷子时,发现已经被其他哲学家拿走了。此时,所有的哲学家都在等待另一根永远拿不到的筷子,就形成了死锁。

二、造成死锁的原因

造成死锁有3个条件,如果出现死锁,那么一定满足下面四个条件,但是,四个条件同时出现,不一定代表就是死锁。

  1. 资源互斥:一个资源只能被一个线程持有。也就是说,一根筷子只能被一个哲学家拿着,如果两个哲学家能同时使用一根筷子,那么就不会出现死锁;
  2. 资源不可抢夺:一个资源一旦被某个线程持有,除非这个线程自己放弃这个资源,否则没有人能抢走。套用在哲学家问题上,就是如果一个哲学家拿起了一根筷子,除非他主动将筷子放下,否则没有人能够将筷子从他的手上抢走;
  3. 循环等待:线程A持有了A资源,线程B持有了B资源,A在等待B释放B资源,B在等待A释放A资源。

三、使用Java模拟哲学家吃饭导致死锁问题

将问题简化为只有两个哲学家,java实现如下。

哲学家:

public class Philosopher implements Runnable {
    private final String name;
    private final Chopstick left;
    private final Chopstick right;

    public Philosopher(int id, Chopstick left, Chopstick right) {
        this.name = "Philosopher-" + id;
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        for (;;) {
            think();
            eat();
        }
    }

    private void eat() {
       synchronized (left) {
           System.out.println(name + " pick up left chopstick " + left);
           left.pickUp();
           synchronized (right) {
               System.out.println(name + " pick up right chopstick " + right);
               right.pickUp();
               doEat();
               System.out.println(name + " put down right chopstick " + right);
               right.putDown();
           }
           System.out.println(name + " put down left chopstick " + left);
           left.putDown();
       }
    }

    private void doEat() {
        System.out.println(name + " is eating.");
        Tools.sleep(1000L);
        System.out.println(name + " finish eating.");
    }

    private void think() {
        System.out.println(name + " is thinking.");
        Tools.sleep(1000L);
        System.out.println(name + " finish thinking.");
    }

    public String getName() {
        return name;
    }
}	

筷子:

class Chopstick {
    private final int id;
    private ChopstickState state;

    public Chopstick(int id) {
        this.id = id;
        this.state = ChopstickState.PUT_DOWN;
    }

    public void pickUp() {
        this.state = ChopstickState.PICK_PU;
    }

    public void putDown() {
        this.state = ChopstickState.PUT_DOWN;
    }

    @Override
    public String toString() {
        return "Chopstick-" + id;
    }
}

筷子状态:

enum ChopstickState {
    PICK_PU,
    PUT_DOWN,
    ;
}

一个简单的辅助类:

public class Tools {
    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序入口:

public class Main {
    public static void main(String[] args) {
        int philosopherNum = 2;
        Chopstick[] chopsticks = createChopstick(philosopherNum);
        Philosopher[] philosophers = createPhilosopher(chopsticks);
        startPhilosopherThreads(philosophers);
    }

    private static Chopstick[] createChopstick(int num) {
        Chopstick[] chopsticks = new Chopstick[num];
        for (int i = 0; i < num; i++) {
            chopsticks[i] = new Chopstick(i);
        }
        return chopsticks;
    }

    private static Philosopher[] createPhilosopher(Chopstick[] chopsticks) {
        int size = chopsticks.length;
        Philosopher[] philosophers = new Philosopher[size];
        for (int i = 0; i < size; i++) {
            int left = i;
            int right = i + 1;
            right = right == size ? 0 : right;
            philosophers[i] = new Philosopher(i, chopsticks[left], chopsticks[right]);
        }
        return philosophers;
    }

    private static void startPhilosopherThreads(Philosopher[] philosophers) {
        for (Philosopher philosopher: philosophers) {
            Thread thread = new Thread(philosopher, philosopher.getName());
            thread.start();
        }
    }
}

启动程序后,我们使用VisualVM查看程序的ThreadDump,部分如下:

"Philosopher-1" #14 prio=5 os_prio=0 tid=0x000001ef72af9800 nid=0xe20 waiting for monitor entry [0x000000cae1dff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at philosopher.Philosopher.eat(Philosopher.java:27)
	- waiting to lock <0x0000000088b19428> (a philosopher.Chopstick)
	- locked <0x0000000088b19440> (a philosopher.Chopstick)
	at philosopher.Philosopher.run(Philosopher.java:18)
	at java.lang.Thread.run(java.base@9/Thread.java:844)

   Locked ownable synchronizers:
	- None

"Philosopher-0" #13 prio=5 os_prio=0 tid=0x000001ef72af8800 nid=0x3074 waiting for monitor entry [0x000000cae1cff000]
   java.lang.Thread.State: BLOCKED (on object monitor)
	at philosopher.Philosopher.eat(Philosopher.java:27)
	- waiting to lock <0x0000000088b19440> (a philosopher.Chopstick)
	- locked <0x0000000088b19428> (a philosopher.Chopstick)
	at philosopher.Philosopher.run(Philosopher.java:18)
	at java.lang.Thread.run(java.base@9/Thread.java:844)

   Locked ownable synchronizers:
	- None

我们可以看出,线程Philosopher-1持有0x0000000088b19440,等待0x0000000088b19428。线程Philosopher-0持有0x0000000088b19428,等待0x0000000088b19440,因而形成死锁。

四、如何解决死锁

解决死锁,只需要将消除形成它的三个必要条件的其中一个即可。我们这里使用一个比较简单的方案:解决循环等待。一个哲学家最多只会等待3秒钟,如果无法得到想要的筷子,那么他就会放下手中的筷子(如果有)。Java实现如下:

哲学家

public class Philosopher implements Runnable {
    private final String name;
    private final Chopstick left;
    private final Chopstick right;

    public Philosopher(int id, Chopstick left, Chopstick right) {
        this.name = "Philosopher-" + id;
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        for (; ; ) {
            think();
            try {
                eat();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void eat() throws InterruptedException {
        Random random = new Random();
        if (left.getLock().tryLock(random.nextInt(3), TimeUnit.SECONDS)) {
            try {
                System.out.println(name + " pick up left chopstick " + left);
                left.pickUp();
                if (right.getLock().tryLock(random.nextInt(3), TimeUnit.SECONDS)) {
                    try {
                        System.out.println(name + " pick up right chopstick " + right);
                        right.pickUp();
                        doEat();
                    } finally {
                        System.out.println(name + " put down right chopstick " + right);
                        right.putDown();
                        right.getLock().unlock();
                    }
                }

            } finally {
                System.out.println(name + " put down left chopstick " + left);
                left.putDown();
                left.getLock().unlock();
            }
        }
    }

    private void doEat() {
        System.out.println(name + " is eating.");
        Tools.sleep(1000L);
        System.out.println(name + " finish eating.");
    }

    private void think() {
        System.out.println(name + " is thinking.");
        Tools.sleep(1000L);
        System.out.println(name + " finish thinking.");
    }

    public String getName() {
        return name;
    }
}

筷子

class Chopstick {
    private final int id;
    private ChopstickState state;
    private final ReentrantLock lock = new ReentrantLock();

    public Chopstick(int id) {
        this.id = id;
        this.state = ChopstickState.PUT_DOWN;
    }

    public void pickUp() {
        this.state = ChopstickState.PICK_PU;
    }

    public void putDown() {
        this.state = ChopstickState.PUT_DOWN;
    }

    public Lock getLock() {
        return lock;
    }

    @Override
    public String toString() {
        return "Chopstick-" + id;
    }
}

其他代码与原有代码相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值