并发-常见笔试题-哲学家进餐

根据哲学家进餐经典问题来巩固并发带来的死锁问题以及解决方式

问题简述

哲学家进餐问题是一个经典的并发编程问题,用来探讨资源竞争和死锁的情况。问题的场景是五个哲学家围坐在一张圆桌旁,每个哲学家面前有一碗饭和一只叉子。哲学家的生活由交替进行的思考和进餐两个活动组成。但是,每个哲学家只能同时使用自己左右两边的叉子来进餐。

经典问题代码

java 复制代码
@Test
public void testCase() {
    Fork f1 = new Fork("1");
    Fork f2 = new Fork("2");
    Fork f3 = new Fork("3");
    Fork f4 = new Fork("4");
    Fork f5 = new Fork("5");
    ExecutorService threadPool = Executors.newFixedThreadPool(5, new ThreadFactory() {
        private volatile int index = 0;

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "t" + (index++));
        }
    });
    threadPool.submit(new PhilosophersTask("1", f1, f5));
    threadPool.submit(new PhilosophersTask("2", f2, f1));
    threadPool.submit(new PhilosophersTask("3", f3, f2));
    threadPool.submit(new PhilosophersTask("4", f4, f3));
    threadPool.submit(new PhilosophersTask("5", f5, f4));
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
    threadPool.shutdown();
}

private static class PhilosophersTask implements Runnable {

    private String name;
    private Fork leftFork;
    private Fork rightFork;

    public PhilosophersTask(String name, Fork left, Fork right) {
        this.name = name;
        this.leftFork = left;
        this.rightFork = right;
    }

    @Override
    public void run() {
        while (true) {
            thinking();
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
            dining();
        }

    }

    private void thinking() {
        System.out.println(name + " is thinking.");
    }

    private void dining() {
        synchronized (leftFork) {
            System.out.println(name + " use left " + leftFork);
            //这一句很关键
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
            synchronized (rightFork) {
                System.out.println(name + " use right " + leftFork);
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
            }
        }
    }
}

/**
 * 叉子
 */
private static class Fork {
    private String name;

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

    @Override
    public String toString() {
        return "Fork " + this.name;
    }
}

执行结果

每个哲学家都拿起左边的叉子,准备用右边的叉子,形成环路等待

csharp 复制代码
1 is thinking.
5 is thinking.
2 is thinking.
3 is thinking.
4 is thinking.
4 use left Fork 4
3 use left Fork 3
2 use left Fork 2
5 use left Fork 5
1 use left Fork 1

解决哲学家就餐问题

使用助手破坏死锁请求不释放的条件

代码如下

java 复制代码
@Test
public void testCase() {
    Fork f1 = new Fork("1");
    Fork f2 = new Fork("2");
    Fork f3 = new Fork("3");
    Fork f4 = new Fork("4");
    Fork f5 = new Fork("5");
    Assistant assistant = new Assistant("a1");
    ExecutorService threadPool = Executors.newFixedThreadPool(5, new ThreadFactory() {
        private volatile int index = 0;

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "t" + (index++));
        }
    });
    threadPool.submit(new PhilosophersTask("1", f1, f5, assistant));
    threadPool.submit(new PhilosophersTask("2", f2, f1, assistant));
    threadPool.submit(new PhilosophersTask("3", f3, f2, assistant));
    threadPool.submit(new PhilosophersTask("4", f4, f3, assistant));
    threadPool.submit(new PhilosophersTask("5", f5, f4, assistant));
    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
    threadPool.shutdown();
}

private static class PhilosophersTask implements Runnable {

    private String name;
    private Fork leftFork;
    private Fork rightFork;
    private Assistant assistant;

    public PhilosophersTask(String name, Fork left, Fork right, Assistant assistant) {
        this.name = name;
        this.leftFork = left;
        this.rightFork = right;
        this.assistant = assistant;
    }

    @Override
    public void run() {
        while (true) {
            thinking();
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
            dining();
        }

    }

    private void thinking() {
        System.out.println(name + " is thinking.");
    }

    private void dining() {
        synchronized (leftFork) {

            if (assistant.num.incrementAndGet() > 4) {
                System.out.println(name + " put down left " + leftFork);
                assistant.num.decrementAndGet();
                return;
            } else {
                System.out.println(name + " use left " + leftFork);
            }
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10));
            synchronized (rightFork) {
                System.out.println(name + " use right " + rightFork);
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
            }
            assistant.num.decrementAndGet();
        }
    }
}

/**
 * 叉子
 */
private static class Fork {
    private String name;

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

    @Override
    public String toString() {
        return "Fork " + this.name;
    }
}

private static class Assistant {
    protected AtomicInteger num = new AtomicInteger(0);
    private String name;

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

执行结果

css 复制代码
1 is thinking.
3 is thinking.
2 is thinking.
5 is thinking.
4 is thinking.
5 use left Fork 5
3 use left Fork 3
2 put down left Fork 2
1 use left Fork 1
2 is thinking.
4 use left Fork 4
2 put down left Fork 2
2 is thinking.
3 use right Fork 2
2 use left Fork 2
3 is thinking.
4 use right Fork 3
4 is thinking.
3 use left Fork 3
5 use right Fork 4
4 use left Fork 4
1 use right Fork 5
5 is thinking.
1 is thinking.
5 use left Fork 5
2 use right Fork 1
2 is thinking.
3 use right Fork 2
1 put down left Fork 1
1 is thinking.
1 use left Fork 1
3 is thinking.
2 use left Fork 2
4 use right Fork 3
4 is thinking.
5 use right Fork 4
3 use left Fork 3
4 use left Fork 4
5 is thinking.
1 use right Fork 5
1 is thinking.
5 use left Fork 5
2 use right Fork 1

使用lock的有限等待

将上述使用synchronized改为tryLock有效时间获取时间锁资源,如果获取不到则不再获取

死锁条件

死锁是指系统中的一组进程(或线程)由于竞争有限的资源而无法继续执行的情况。产生死锁的常见条件有以下四个:

  1. 互斥条件(Mutual Exclusion):至少有一个资源同时只能被一个进程(或线程)占用,即当一个进程(或线程)访问该资源时,其他进程(或线程)无法同时访问。
  2. 请求与保持条件(Hold and Wait):一个进程(或线程)在等待获取其他进程(或线程)占用的资源时,保持已经占有的资源不释放。
  3. 不可剥夺条件(No Preemption):已经分配给一个进程(或线程)的资源不能被强制性地剥夺,只能由占有该资源的进程(或线程)主动释放。
  4. 循环等待条件(Circular Wait):系统中存在一个进程(或线程)的等待链,使得每个进程(或线程)都在等待下一个进程(或线程)所占有的资源。

哲学家进餐针对1 3 4无法更改,只能在2 请求与保持上下功夫在解决

相关推荐
曹牧4 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
KYGALYX4 小时前
服务异步通信
开发语言·后端·微服务·ruby
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
爬山算法4 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7255 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎5 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄5 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
忆~遂愿5 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds5 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化