likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 21

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

问题简述

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

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

经典问题代码

@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;
    }
}

执行结果

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

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

解决哲学家就餐问题

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

代码如下

@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;
    }
}

执行结果

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