Thking in Java---从哲学家就餐问题看死锁现象

我们知道一个对象可以有synchronized方法或其他形式的加锁机制来防止别的线程在互斥还没释放的时候就访问这个对象。而且我们知道线程是会变成阻塞状态的(挂起),所以有时候就会发生死锁的情况:某个任务在等待另一个任务,而后者又在等待其它任务,这样一直下去,知道这个链条下的任务又在等待第一个任务释放锁,这样就形成了一个任务之间相互等待的连续循环,没有任务可以继续的情况。死锁的最大问题在于它发生的几率非常小,并不是我们一运行程序它就死锁了,而是会不知道那个时候程序就死锁了并且我们很难重现当时出现死锁的情况。在这篇博客里我们只是从哲学家就餐问题的程序中感受下死锁现象,随便结合分析下出现死锁的几个条件,并不会讨论如何避免/解决死锁问题。

哲学家就餐问题是一个经典的关于死锁的问题,其大概的描述是:有五个哲学家,他们会花部分时间思考,花部分时间就餐。当他们思考的时候,他们不需要共享任何资源互补影响。而当他们就餐时,因为这里只有五只筷子,在他们每人都需要两只筷子的情况下,就会形成对筷子的竞争。这个问题并不是说百分百的会死锁,仅仅是存在这种可能而已。下面的代码就演示了这个问题,在注释里有对更多的细节的解释:

package IO;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
 * 通过模拟哲学家问题来观察死锁的产生
 * 下面的代码是模拟的五个哲学家和五根筷子的经典哲学家问题
 * */

//共享资源:筷子类
class Chopstick{
    private boolean token=false; //该筷子是否被使用了

    //使用筷子,如果该筷子已被别的线程使用,则当前线程调用wait()挂起
    public synchronized void take() throws InterruptedException{
           while(token){
               wait();
           }
           token=true;
    }

    //筷子使用完以后,放下筷子,并唤醒其它正在wait()的线程
    public synchronized void drop(){
        token=false;
        notifyAll();
    }
}

class Philosopers implements Runnable{

    //左边和右边的筷子
    private Chopstick left;
    private Chopstick right;

    private final int id;//哲学家使用筷子的编号
    private final int pauseFactor; //暂停因子
    private Random rand = new Random(200);

    public Philosopers(Chopstick left,Chopstick right,int id,int pauseFactor){
        this.left=left;
        this.right=right;
        this.id=id;
        this.pauseFactor=pauseFactor;
    }

    //暂停随机时间
    private void pause() throws InterruptedException{
        TimeUnit.MILLISECONDS.sleep(pauseFactor*rand.nextInt(100));
    }

    public void run(){
        try{
            while(!Thread.interrupted()){
                System.out.println(this+" "+"thinking........... ");//表示正在思考
                pause();//模拟思考时间
                System.out.println(this+" 取得左边的筷子");
                left.take(); //取得左边的筷子
                System.out.println(this+" 取得右边的筷子");
                right.take();
                System.out.println(this+"Eating...........");//就餐
                pause();//模拟就餐时间

                //放下筷子
                left.drop();
                right.drop();
            }
        }catch(InterruptedException ex){
            System.out.println(this+" 通过中断异常退出");
        }
    }

    public String toString(){
        return (id+1)+"号哲学家 ";
    }
}

public class test {

   public static void main(String[] args) throws Exception{

       //可以通过调整pauseFactor从而调整哲学家思考的时间
       //思考时间越短则线程间对共享资源的竞争越强越容易产生死锁问题
       //pauseFacotr等于0的时候几乎每次都可以看到死锁问题的产生
       int pauseFactor=0;
       int size=5;
       Chopstick[] chopstick = new Chopstick[size];

       //五只筷子
       for(int i=0;i<size;i++){
           chopstick[i] = new Chopstick();
       }

       ExecutorService exec = Executors.newCachedThreadPool();
       //产生5个哲学家线程
       for(int i=0; i<size; i++){
          exec.execute(new Philosopers(chopstick[i],chopstick[(i+1)%size],i,pauseFactor));

          /*//下面的代码通过防止循环等待而阻止了死锁的产生
           //让前四位哲学家总是先拿左边的筷子,再拿右边的筷子,而让第五位哲学家先拿右边的筷子
           //这样可以打破循环等待的条件,实际上这时候第五位哲学家总是会由于取不到右边的筷子
           //而阻塞(它右边的筷子已经被第一位哲学家取了),所以也就不会去拿它左边的筷子,从而
           //第四位哲学家总是可以取得两只筷子第一个就餐,从而不会产生循环等待的结果(从输出中可以看到这一点).
           if(i<(size-1)){
               exec.execute(new Philosopers(chopstick[i],chopstick[(i+1)%size],i,pauseFactor));
           }
           else{
               exec.execute(new Philosopers(chopstick[(i+1)%size],chopstick[i],i,pauseFactor));
           }
           */
       }

       exec.shutdown();
       System.out.println("press ‘Enter‘ to quit");
       System.in.read();
       exec.shutdownNow();
   }
}

在上面的程序中我们可以设定了哲学家的思考时间和就餐时间都为一个随机时间,并且我们可以通过pauseFactor来对其进行调节,多次设定这个值然后运行程序就会发现当pauseFactor的值较小的时候发生死锁的概率就比较大,当pauseFactor为0即哲学家不花时间进行思考而总是去抢筷子的时候,几乎每次都会发生死锁。这就说明对资源的竞争越激烈就越容易发生死锁。

学过操作系统的都知道要发生死锁有四个条件需要满足,下面我们就结合上面的问题来分析下这四个条件:

1).互斥。线程使用的资源中至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用

2).至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须持有一个Chopstick并正在等待另一根。

3).资源不能被任务抢占,任务必须把释放资源当成普通事件。在上面中Philosopher不会去抢其它Philosopher手中的Chopstick。

4).必须有循环等待,这时候一个任务等待其它任务所持有的资源,后者又在等待另一个任务持有的资源,这样一直下去,直到一个任务在等待第一个任务所持有的资源。在上面的代码中每个Philosopher都会试图得到左边的Chopstick然后得到右边的Chopstick,这样就产生了循环等待。

死锁出现必须要满足上面四个条件,所以避免死锁的方法就是破坏四个条件中的一个。上面有一段注释掉的代码通过调整最后一位哲学家获取Chopstick的顺序(与其它哲学家相反)而达到了破坏第四个循环等待条件的效果,从而防止了死锁的产生。其它的防止死锁的方法就不在这里讨论了。Java中并没有从语言层面上有防止死锁的措施,所以要防止死锁的产生,只有靠我们自己的小心谨慎了。

时间: 2024-10-11 07:21:32

Thking in Java---从哲学家就餐问题看死锁现象的相关文章

JAVA并发,经典死锁案例-哲学家就餐

转自:http://blog.csdn.net/tayanxunhua/article/details/38691005 死锁经典案例:哲学家就餐. 这个案例会导致死锁. 通过修改<Java编程思想4>一书中的案例,来做实验,代码更易理解,结果也相对容易控制. 附代码: 筷子类: 1 package com.tyxh.ch21.c6; 2 3 public class Chopstick { 4 private boolean taken = false;//判断是此筷子是否被拿起 5 pub

Java多线程,哲学家就餐问题

问题描述:一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条.哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭.上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象. 解决办法: 1.添加一个服务生,只有当经过服务生同意之后才能拿筷子,服务生负责避免死锁发生. 2.每个哲学家必须确定自己左右手的筷子都可用的时候,才能同时拿起两只筷子进餐,吃完之后同时放下两只筷子. 3.规定每个哲学家拿筷子时必须拿序号小的那只,这样最后

从哲学家就餐问题彻底认识死锁

第一节  哲学家就餐问题 第二节  什么是死锁 第三节  死锁的定义 第四节  死锁发生的条件 第五节  如何避免死锁 5.1 动态避免,银行家算法(杠杆分配),在资源分配上下文章 5.2 静态避免,从任务代码上避免死锁 第六节  死锁的综合治理 第一节  哲学家就餐问题 假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考.吃东西的时候,他们就停止思考,思考的时候也停止吃东西.餐桌中间有一大碗意大利面,每两个哲学家之间有一双筷子.因为用一只筷子很难吃到意大利面,所以假设哲学

IPC问题-哲学家就餐

如上图,有五位哲学家,盘中的食物只有左右两个叉子都拿起才能吃.哲学家在桌上只有思考(等待)和吃面(执行).看起来最多是只有2个人能同时吃. 版本一:这个思路的最糟糕的就是都拿起左边叉子,那样都没法吃了,直接死锁. 版本二:改进版本一,如果拿起左边叉子,先看右边是否能用,不可用的话放下左边叉子等待一段时间再运行.这个思路的问题就是,会产生某个时刻每个人都拿起左叉子,又放下,又拿起,虽然没有死锁,但是没有人在吃面,消耗了性能但是任务没有进展,造成饥饿. 版本三:改进版本二,每次等待右边是否能用的每个

【转】成为Java顶尖程序员 ,看这11本书就够了

成为Java顶尖程序员 ,看这11本书就够了 转自:http://developer.51cto.com/art/201512/503095.htm 以下是我推荐给Java开发者们的一些值得一看的好书.但是这些书里面并没有Java基础.Java教程之类的书,不是我不推荐,而是离我自己学习 Java基础技术也过去好几年了,我学习的时候看的什么也忘了,所以我不能不负责任地推荐一些我自己都没有看过的书给大家. 作者:来源:攻城狮之家|2015-12-31 09:55 收藏 分享 “学习的最好途径就是看

Java基础-接口中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产。请编写一个java应用程序描述上述的体制现象。 要求如下: (1)该应用程序中有一个“党中央”接口:CentralPartyCommittee,该接口中 有个“坚持党的领导”方法:void partyLeader() (2)该应用程序中有一个“国务院”抽象类:StateCouncil,

36.中国特色社会主义的体制中有这样的现象:地方省政府要坚持党的领导和按 照国务院的指示进行安全生产.请编写一个java应用程序描述上述的体制现象. 要求如下: (1)该应用程序中有一个“党中央”接口:CentralPartyCommittee,该接口中 有个“坚持党的领导”方法:void partyLeader() (2)该应用程序中有一个“国务院”抽象类:StateCouncil,该抽象类中有个“安 全生产”的抽象方法:abstract void safetyInProduction() (

java中同步嵌套引起的死锁事例代码

/* 目的:自己写一个由于同步嵌套引起的死锁! 思路:多个线程在执行时,某一时刻,0-Thread绑定了LockA锁,1-Thread绑定了LockB锁! 当0-Thread要去绑定LockB锁时 和 1-Thread要去绑定LockA锁时都不能绑定,此时两个线程不能继续进行! */ class Ticket implements Runnable{ public boolean flag; Ticket(boolean flag){ this.flag = flag; } Ticket(){

成为Java顶尖程序员 ,看这11本书就够了

学习的最好途径就是看书 "学习的最好途径就是看书",这是我自己学习并且小有了一定的积累之后的第一体会.个人认为看书有两点好处: 1.能出版出来的书一定是经过反复的思考.雕琢和审核的,因此从专业性的角度来说,一本好书的价值远超其他资料 2.对着书上的代码自己敲的时候方便 "看完书之后再次提升自我的最好途径是看一些相关的好博文",我个人认为这是学习的第二步,因为一本书往往有好几百页,好的博文是自己看书学习之后的一些总结和提炼,对于梳理学习的内容很有好处,当然这里不是说自

Java进阶知识点2:看不懂的代码 - 协变与逆变

要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那么我们可以在任何时候使用MochaCoffee类的引用去替换Coffee类的引用(重写函数时,形参必须与重写函数完全一致,这是一处列外),而不会引发编译错误(至于会不会引发程序功能错误,取决于代码是否符合里氏替换原则). 简而言之,如果B类是A类的派生类,那么B类的引用可以赋值给A类的引用. 赋值的方式最常见