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

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

问题简述

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

经典问题代码

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 请求与保持上下功夫在解决

相关推荐
ask_baidu20 分钟前
监控Source端Pg对Flink CDC的影响
java·大数据·postgresql·flink
李慕婉学姐21 分钟前
Springboot旅游景点管理系统2fj40iq6(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
技术宅星云27 分钟前
0x00.Spring AI Agent开发指南专栏简介
java·人工智能·spring
Arenaschi32 分钟前
关于垃圾的CSDN
java·网络·chrome·笔记·其他·oracle·pdf
欧洵.32 分钟前
深入理解TCP协议
java·网络·tcp/ip
砚边数影33 分钟前
KingbaseES基础(二):SQL进阶 —— 批量插入/查询 AI 样本数据实战
java·数据库·人工智能·sql·ai
独自归家的兔37 分钟前
Java性能优化实战:从基础调优到系统效率倍增 -2
java·开发语言·性能优化
独自归家的兔42 分钟前
Java性能优化实战:从基础调优到系统效率倍增 - 1
java·开发语言·性能优化
蓝眸少年CY43 分钟前
(第八篇)spring cloud之zuul路由网关
后端·spring·spring cloud