哲学家进餐问题(java模拟死锁及解决方案)

一、问题描述

哲学家进餐问题是由 Dijkstra 提出并解决的,该问题是描述有五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐完毕,放下筷子继续思考。如下图所示:

二、代码模拟实现

经分析可知,放在桌子上的筷子是临界资源,在一段时间内只允许一位哲学家使用。为了实现对筷子的互斥使用,可以用一个信号量表示一只筷子,由这五个信号量构成信号量数组。其java代码实现如下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

/**
 *  @author: hh
 *  @date: 2021/1/12 20:31
 *  @description: 哲学家进餐,模拟哲学家同时拿起左手边筷子
 */
public class PhilosopherMeal extends Thread {

    /**
     * 哲学家名称前缀
     */
    private static String NAME_PREFIX = "哲学家";

    /**
     * 哲学家编号
     */
    private int philosopherNum;


    /**
     * 模拟筷子资源信号量
     */
    private volatile List<Semaphore> semaphores;


    public PhilosopherMeal(int philosopherNum,List<Semaphore> semaphores){
        super(PhilosopherMeal.NAME_PREFIX+String.valueOf(philosopherNum));
        this.philosopherNum = philosopherNum;
        this.semaphores = semaphores;
    }

    @Override
    public void run() {

        try {
            //哲学家先拿起左手边筷子
            semaphores.get(this.philosopherNum).acquire();
            //哲学家拿起右手边筷子
            semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).acquire();

            System.out.println(this.getName()+"获取到了筷子"+String.valueOf(this.philosopherNum)+"和"+ String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");

        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            semaphores.get(this.philosopherNum).release();
            semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).release();
        }
    }

    /**
     * 获取当前哲学家右手边的筷子编号
     *
     * @param philosopherNum :当前哲学家编号
     * @return :右手边筷子编号
     */
    private int getRightSemaphoreIndex(int philosopherNum){
        return (philosopherNum - 1 < 0) ? semaphores.size() - 1 : philosopherNum - 1;
    }

    public static void main(String[] args){
        List<Semaphore> semaphores = new ArrayList<>();
        int counter = 5;
        for(int i = 0 ; i < counter ; i++){
            semaphores.add(new Semaphore(1));
        }
        for(int i = 0 ; i < counter ; i++){
            new PhilosopherMeal(i,semaphores).start();
        }
    }
}

运行结果如下图:

在以上描述中,当哲学家饥饿时,总是先去拿他左边的筷子,即执行 semaphores.get(this.philosopherNum).acquire();成功后,再去拿他右边的筷子,即执行 semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).acquire();又成功后便可进餐。进餐完毕,又先放下他左边的筷子,然后再放右边的筷子。虽然,上述解法可保证不会有两个相邻的哲学家同时进餐,但有可能引起死锁。假如五位哲学家同时饥饿而各自拿起左边的筷子时,就会使五个信号量均为 0; 当他们再试图去拿右边的筷子时,都将因无筷子可拿而无限期地等待。对于这样的死锁问题。如下代码模拟死锁在代码中加入一行Thread.sleep(1000); 即模拟五个哲学家同时拿到左手边筷子,代码如下所示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;

/**
 *  @author: hh
 *  @date: 2021/1/12 20:31
 *  @description: 哲学家进餐,模拟哲学家同时拿起左手边筷子,使其产生死锁
 */
public class PhilosopherMeal extends Thread {

    /**
     * 哲学家名称前缀
     */
    private static String NAME_PREFIX = "哲学家";

    /**
     * 哲学家编号
     */
    private int philosopherNum;


    /**
     * 模拟筷子资源信号量
     */
    private volatile List<Semaphore> semaphores;


    public PhilosopherMeal(int philosopherNum,List<Semaphore> semaphores){
        super(PhilosopherMeal.NAME_PREFIX+String.valueOf(philosopherNum));
        this.philosopherNum = philosopherNum;
        this.semaphores = semaphores;
    }

    @Override
    public void run() {

        try {
            //哲学家先拿起左手边筷子
            semaphores.get(this.philosopherNum).acquire();
            //睡眠1s,使其它哲学家有时间拿左手边的筷子
            Thread.sleep(1000);
            //哲学家拿起右手边筷子
            semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).acquire();

            System.out.println(this.getName()+"获取到了筷子"+String.valueOf(this.philosopherNum)+"和"+ String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");

        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            semaphores.get(this.philosopherNum).release();
            semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).release();
        }
    }

    /**
     * 获取当前哲学家右手边的筷子编号
     *
     * @param philosopherNum :当前哲学家编号
     * @return :右手边筷子编号
     */
    private int getRightSemaphoreIndex(int philosopherNum){
        return (philosopherNum - 1 < 0) ? semaphores.size() - 1 : philosopherNum - 1;
    }

    public static void main(String[] args){
        List<Semaphore> semaphores = new ArrayList<>();
        int counter = 5;
        for(int i = 0 ; i < counter ; i++){
            semaphores.add(new Semaphore(1));
        }
        for(int i = 0 ; i < counter ; i++){
            new PhilosopherMeal(i,semaphores).start();
        }
    }
}

三、解决死锁问题

可采取以下几种解决方法:

1. 至多只允许有四位哲学家同时去拿左边的筷子,最终能保证至少有一位哲学家能够进餐,并在用毕时能释放出他用过的两只筷子,从而使更多的哲学家能够进餐。

2. 仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐。

3. 规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则相反。按此规定,将是 1、2 号哲学家竞争 1 号筷子;3、4 号哲学家竞争 3 号筷子。即五位哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获得两只筷子而进餐。

本文只实现2、3两种方法,第1个方法读者可参考代码自行实现,方案2代码如下所示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 *  @author: hh
 *  @date: 2021/1/12 20:31
 *  @description: 哲学家进餐解决方法1:规定奇数号哲学家先拿他左边的筷子,然后再去拿右边的筷子,而偶数号哲学家则
 * 相反。按此规定,将是 0、1 号哲学家竞争 0号筷子;2、3 号哲学家竞争 2 号筷子。即五位
 * 哲学家都先竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一位哲学家能获
 * 得两只筷子而进餐。
 */
public class PhilosopherMealSolution1 extends Thread {

    /**
     * 哲学家名称前缀
     */
    private final static String NAME_PREFIX = "哲学家";

    /**
     * 哲学家编号
     */
    private final int philosopherNum;


    /**
     * 模拟筷子资源信号量
     */
    private volatile ArrayList<Semaphore> semaphores;


    public PhilosopherMealSolution1(int philosopherNum,ArrayList<Semaphore> semaphores){
        super(PhilosopherMealSolution1.NAME_PREFIX+String.valueOf(philosopherNum));
        this.philosopherNum = philosopherNum;
        this.semaphores = semaphores;
    }

    @Override
    public void run() {

        try {
            if(this.philosopherNum % 2 == 0){
                //哲学家先拿起左手边筷子
                semaphores.get(this.philosopherNum).acquire();
                //睡眠1s,使其它哲学家有时间拿左手边的筷子
               Thread.sleep(1000);
                //哲学家拿起右手边筷子
                semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).acquire();
            }else{
                //哲学家拿起右手边筷子
                semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).acquire();
                //睡眠1s,使其它哲学家有时间拿左手边的筷子
               Thread.sleep(1000);
                //哲学家先拿起左手边筷子
                semaphores.get(this.philosopherNum).acquire();
            }


            System.out.println(this.getName()+"获取到了筷子"+String.valueOf(this.philosopherNum)+"和"+ String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");

        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            semaphores.get(this.philosopherNum).release();
            semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)).release();
        }
    }

    /**
     * 获取当前哲学家右手边的筷子编号
     *
     * @param philosopherNum :当前哲学家编号
     * @return :右手边筷子编号
     */
    private int getRightSemaphoreIndex(int philosopherNum){
        return (philosopherNum - 1 < 0) ? semaphores.size() - 1 : philosopherNum - 1;
    }

    public static void main(String[] args){
        ArrayList<Semaphore> semaphores = new ArrayList<>();
        int counter = 5;
        for(int i = 0 ; i < counter ; i++){
            semaphores.add(new Semaphore(1));
        }
        for(int i = 0 ; i < counter ; i++){
            new PhilosopherMealSolution1(i,semaphores).start();
        }
    }
}

方案3代码如下所示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

/**
 *  @author: hh
 *  @date: 2021/1/12 20:31
 *  @description: 哲学家进餐解决方法2:仅当哲学家的左、右两只筷子均可用时,才允许他拿起筷子进餐
 */
public class PhilosopherMealSolution2 extends Thread {

    /**
     * 哲学家名称前缀
     */
    private final static String NAME_PREFIX = "哲学家";

    /**
     * 用于控制and 信号量访问的锁
     */
    private ReentrantLock semaphoreLock;

    /**
     * 哲学家编号
     */
    private final int philosopherNum;

    /**
     * 模拟筷子资源信号量
     */
    private volatile List<Semaphore> semaphores;



    public PhilosopherMealSolution2(int philosopherNum, List<Semaphore> semaphores,ReentrantLock semaphoreLock){
        super(PhilosopherMealSolution2.NAME_PREFIX+String.valueOf(philosopherNum));
        this.philosopherNum = philosopherNum;
        this.semaphores = semaphores;
        this.semaphoreLock = semaphoreLock;
    }

    @Override
    public void run() {

        try {
            //只有同时申请到左手边和右手边的筷子才能继续向下执行
            this.await(semaphores.get(this.philosopherNum),semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)));
            Thread.sleep(1000);

            System.out.println(this.getName()+"获取到了筷子"+String.valueOf(this.philosopherNum)+"和"+ String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");

        } catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            this.release(semaphores.get(this.philosopherNum),semaphores.get(this.getRightSemaphoreIndex(this.philosopherNum)));
        }
    }

    /**
     * 获取当前哲学家右手边的筷子编号
     *
     * @param philosopherNum :当前哲学家编号
     * @return :右手边筷子编号
     */
    private int getRightSemaphoreIndex(int philosopherNum){
        return (philosopherNum - 1 < 0) ? semaphores.size() - 1 : philosopherNum - 1;
    }

    /**
     *and 信号量,只有数组中的信号量都是同时可获取的,才可以继续往下走,否则则阻塞
     *
     * @param semaphores :信号量数组
     */
    private void await(Semaphore ... semaphores){
        semaphoreLock.lock();
        try{
            for(Semaphore semaphore : semaphores){
                semaphore.acquire();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            semaphoreLock.unlock();
        }
    }

    /**
     * 同时释放多个信号量
     *
     * @param semaphores : 信号量数组
     */
    private void release(Semaphore ... semaphores){
        try{
            for(Semaphore semaphore : semaphores){
                semaphore.release();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        List<Semaphore> semaphores = new ArrayList<>();
        ReentrantLock semaphoreLock = new ReentrantLock();
        int counter = 5;
        for(int i = 0 ; i < counter ; i++){
            semaphores.add(new Semaphore(1));
        }
        for(int i = 0 ; i < counter ; i++){
            new PhilosopherMealSolution2(i,semaphores,semaphoreLock).start();
        }
    }
}

 

  • 12
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值