解决哲学家就餐问题

1.什么是哲学家就餐问题

假如说有五个哲学家,他们坐在一个桌子上同时吃饭,吃完饭后,就思考问题,每个人的左右都有一支筷子总共有五只筷子.
那么如何让这五个人都能够同时吃到饭?
I.问题转换:哲学家就相当于五个线程,同时竞争五个资源(五根筷子相当于五个资源).那么如何使五个线程都能够使用各自的资源从而不产生死锁呢?
2.什么是死锁:
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,他们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在相互等待的进程称为死锁进程.
这种说法对线程也同样适用
3.死锁产生的四个必要条件:
      1.互斥(一个资源同一时间只能被一个线程所使用)
      2.请求与保持(一个线程正在请求一个资源,对原来占有的资源保持不放)这是说它自己不放开资源
      3.不可剥夺:线程运行过程中,对于已获取的资源,在未使用完之前,不能强行剥夺这是说别的线程不能强行占有它所获得的资源
      4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
      5.以上条件有一条不满足就不会造成死锁

2.举个死锁生活中的例子

在这里插入图片描述
3.举个死锁Java代码中的例子

@Slf4j
public class MyTest {
    private static Object resource01 = new Object();
    private static Object resource02 = new Object();
    //因为代码为演示代码没有使用线程池来获取线程
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource01) {
                log.info("使用资源1");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource02) {
                    log.info("使用资源2");
                }
            }
        }, "t1").start();

        new Thread(() -> {
            synchronized (resource02) {
                log.info("使用资源2");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resource01) {
                    log.info("使用资源1");
                }
            }
        }, "t2").start();
    }
}

4.先设计哲学家就餐问题,线程实现

分析:
1.根据面向对象的思维,我们把哲学家看作一个类,筷子看作一个类,
因为是哲学家使用筷子所以哲学家是线程所以要继承thread并且哲学家使用筷子,而且哲学家没有筷子不能吃饭所以哲学家和筷子的关系为关联关系,即筷子类作为哲学家类中一个实例变量.

1.哲学家类:

@Slf4j
public class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;
    String name;

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

    }

    @Override
    public void run() {
        while (true) {
            //尝试获得左手筷子.
            log.debug("等待筷子:" + left.name);
            synchronized (left) {
                System.out.println(name + "获取筷子:" + left.name);
                //尝试获得右手筷子
                log.debug("等待筷子:" + right.name);
                synchronized (right) {
                    eat(name);
                }
            }
        }
    }

    private void eat(String name) {
        log.debug("eating...");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug(name + "吃完了...");
    }
}

2.筷子类:

class Chopstick {
    String name;

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

    @Override
    public String toString() {
        return "筷子{" + name + "}";
    }
}

3.进行测试:

class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        /**
         *  1 2
         *  2 3
         *  3 4
         *  4 5
         *  1 5
         *
         *  1 -->等待2   2--->等待3     3-->等待4 4-->等待5 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();


    }
}

分析:此时会产生死锁.因为我们看,因为加了synchronizedc1所以满足:互斥,请求与保持,不可剥夺,又因为从代码中分析c1等待c2 , c2 在等待c3 ,c3在等待c4c4在等待c5,c5在等待c1 所以满足死锁的四个充分条件.

5.分析解决哲学家就餐的死锁问题

分析1:
      1.因为只要不满足这四个条件的任意一个都不会产生死锁问题
      2.我们先来想一个最简单的做法,从代码中可以看到c1等待c2 ,c2 在等待c3 ,c3在等待c4c4在等待c5,c5在等
         待c1.所以我们只需破坏循环等待条件给他们吃饭加个限制,比如说我们不让阿基米德先拿第五根筷子,我         们也是先让它拿第一根筷子然后就破环了循环等待的条件.就不会产生死锁了.

  Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        /**
         *  1 2
         *  2 3
         *  3 4
         *  4 5
         *  1 5
         *
         *  1 -->等待2   2--->等待3     3-->等待4 4-->等待5 5不做等待
         *
         */
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c1, c5).start();

分析2:
首先我们要分析能不能破环互斥条件,假如说我们破坏了互斥条件,那么在多线程访问的过程中就会产生指令的交错产生线程不安全.所以这个我们可以先排除.
其次我们可不可以破坏不可剥夺和请求与保持条件呢?这两个条件的支撑是因为加了synchronized锁,
synchronized锁保证了没有执行完就不能释放资源且资源只能被占用.因为synchronized是jvm底层实现的所以说我们不能在synchronized上做文章,那么有没有能代替synchronized的锁并且还能够不满足时释放资源,而且还能保证互斥条件呢?当然有ReentrantLock 下面就让我们使用ReentrantLock来解决这个问题吧.

@Slf4j
class Philosophers extends Thread {
    Chopsticks left;
    Chopsticks right;

    public Philosophers(String name, Chopsticks left, Chopsticks right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            /* synchronized (left) {
                 synchronized (right) {
                     eat();
                 }
             }*/
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("吃饭....");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("吃完了...");
    }
}

class Chopsticks extends ReentrantLock {
    String name;

    public Chopsticks(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopsticks{" +
                "name='" + name + '\'' +
                '}';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值