哲学家就餐死锁问题及解决方案

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了哲学家就餐死锁问题及解决方案相关的知识,希望对你有一定的参考价值。

1、哲学家用餐问题描述

可参考百度!哲学家用餐问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BR3fA67-1623208547023)(picture/哲学家问题.png)]

有五位哲学家,围坐在圆桌旁。

  • 他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。
  • 吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。
  • 如果筷子被身边的人拿着,自己就得等待

2、代码模拟哲学家问题产生死锁的情况

筷子类

@Data
public class Chopstick {
    /**
     * 筷子名称
     */
    String name;

    public Chopstick(final String name) {
        this.name = name;
    }
}

哲学家类

@Slf4j
public class Philosopher extends Thread {
    /**
     * 左筷子
     */
    Chopstick left;
    /**
     * 右筷子
     */
    Chopstick right;

    public Philosopher(final String name, final Chopstick left, final Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() {
        log.info("{} eating.....", Thread.currentThread().getName());
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            // 获得左手筷子
            synchronized (this.left) {
                // 获得右手筷子
                synchronized (this.right) {
                    // 吃饭
                    this.eat();
                }
                // 放下右手筷子
            }
            // 放下左手筷子
        }
    }
}

测试类

public class PhilosopherTest {

    public static void main(final String[] args) {
        final Chopstick c1 = new Chopstick("1");
        final Chopstick c2 = new Chopstick("2");
        final Chopstick c3 = new Chopstick("3");
        final Chopstick c4 = new Chopstick("4");
        final Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

运行结果

09:12:03.018 [苏格拉底] INFO com.wp.juc.hm.question.philosopher.Philosopher - 苏格拉底 eating.....
09:12:03.018 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....
09:12:03.123 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....
09:12:03.123 [苏格拉底] INFO com.wp.juc.hm.question.philosopher.Philosopher - 苏格拉底 eating.....
09:12:03.226 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....
09:12:03.226 [苏格拉底] INFO com.wp.juc.hm.question.philosopher.Philosopher - 苏格拉底 eating.....
09:12:03.331 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....
09:12:03.432 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....
09:12:03.534 [亚里士多德] INFO com.wp.juc.hm.question.philosopher.Philosopher - 亚里士多德 eating.....

通过jconsole来检查有没有发生死锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PQEnHb5m-1623208547025)(picture/哲学家问题死锁.png)]

可以看到,多个线程运行一段时间后就卡住了,产生了死锁。

3、死锁问题解决

我们知道产生死锁一定要同时具备四个条件:互斥、持有并不释放、不可剥夺、循环等待。所以要解决死锁问题我们只要打破其中的一个条件就可以解决死锁问题!!!

我们这里选择破坏第二个条件(持有并且不释放条件),使用带时间的等待,当线程持有一把锁去申请另一把锁的时候,如果获取不到,则等待一定的时间后释放自己持有的锁。

代码示例

筷子类(每根筷子是一把锁)

// 注意这里继承了ReentrantLock类,每个筷子是一把锁
public class Chopstick02 extends ReentrantLock {
    /**
     * 筷子名称
     */
    String name;

    public Chopstick02(final String name) {
        super();
        this.name = name;
    }
}

哲学家类

@Slf4j
public class Philosopher02 extends Thread {
    /**
     * 左筷子
     */
    Chopstick02 left;
    /**
     * 右筷子
     */
    Chopstick02 right;

    public Philosopher02(final String name, final Chopstick02 left, final Chopstick02 right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    private void eat() {
        log.info("{} eating.....", Thread.currentThread().getName());
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            // 获得左手筷子,0.1秒后还未获得就跳过
            if (this.left.tryLock()) {
                try {
                    // 获得右手筷子
                    if (this.right.tryLock(100, TimeUnit.MILLISECONDS)) {
                        try {
                            this.eat();
                        } finally {
                            // 释放锁
                            this.right.unlock();
                        }
                    }
                } catch (final InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    this.left.unlock();
                }
            }
        }
    }
}

测试类

public class PhilosopherTest02 {

    public static void main(final String[] args) {
        final Chopstick02 c1 = new Chopstick02("1");
        final Chopstick02 c2 = new Chopstick02("2");
        final Chopstick02 c3 = new Chopstick02("3");
        final Chopstick02 c4 = new Chopstick02("4");
        final Chopstick02 c5 = new Chopstick02("5");
        new Philosopher02("苏格拉底", c1, c2).start();
        new Philosopher02("柏拉图", c2, c3).start();
        new Philosopher02("亚里士多德", c3, c4).start();
        new Philosopher02("赫拉克利特", c4, c5).start();
        new Philosopher02("阿基米德", c5, c1).start();
    }

}

可以看到,使用ReentrantLock来替代synchronized锁,采用带有时间限制的方式获取锁就可以打破死锁的条件,从而避免死锁。

以上是关于哲学家就餐死锁问题及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

关于哲学家就餐问题中wait()的运用,求大神解释。以下这些代码是对的还是错的?是不是解决了死锁问题

从哲学家就餐问题彻底认识死锁

死锁例证:哲学家就餐

死锁例证:哲学家就餐

死锁例证:哲学家就餐

死锁例证:哲学家就餐