哲学家就餐问题
来源:互联网 发布:知著投资 编辑:程序博客网 时间:2024/06/07 19:40
本文对多线程中经典问题-哲学家就餐问题用Java语言描述,给出了三种常见的解决方法,实现了其中的两种。
一、问题描述:
在一个圆桌上,有n个哲学家,n只筷子,每个哲学家左右两边各返一只筷子。哲学家可以进行思考和吃饭,思考时,不获取筷子。吃饭时,必须同时获得左右两只筷子才能吃饭(先获取左边的再获取右边的)。
二、Java实现哲学家就餐问题
实现Chopstick类,每个筷子有个编号index,还有个属性isUsing表示是否正在使用,如果isUsing为true,后访问的哲学家对象就必须等待直到isUsing为false才能访问
package Blog.philosopher;public class Chopstick { private int index; private boolean isUsing = false; public Chopstick(int index){ this.index = index; } //修改toString方法,因为System.out.println()输出对象都是调用toString方法 @Override public String toString() { return "Chopstick [index = " + index + "]"; } //对于一个成员方法加synchronized关键字,作用范围是从调用该方法开始,作用对象是该方法所在的对象 public synchronized void take() throws InterruptedException{ while(isUsing){ wait(); } isUsing = true; } //使用结束,通知所有其他线程 public synchronized void drop(){ isUsing = false; notifyAll(); } public int getIndex() { return index; } public boolean getIsUsing(){ return isUsing; }}
实现Philosopher类
package Blog.philosopher;public class Philosopher implements Runnable{ protected Chopstick left; protected Chopstick right; protected int index; protected int thinkTime; protected int eatTime; public Philosopher(Chopstick left, Chopstick right, int index) { this.left = left; this.right = right; this.index = index; } //sleep()方法让出CPU,不释放资源。 public void thinking() throws InterruptedException{ System.out.println(this + "thinking"); Thread.sleep(thinkTime); } public void eating() throws InterruptedException{ System.out.println(this + "eating"); Thread.sleep(eatTime); } @Override public void run() { try{ while ( !Thread.interrupted() ){ thinking(); System.out.println(this + "take the left"); left.take(); Thread.sleep(10); System.out.println(this + "going to take the right"); right.take(); System.out.println(this + "has took the right"); eating(); left.drop(); right.drop(); } }catch (Exception e){ e.printStackTrace(); } } @Override public String toString() { return "Philosopher [index = " + index + "]"; }}
哲学家先进行思考,思考结束先获取左边的筷子,如果获取了左边的,再获取右边的筷子,如果没有获得则一直等待直到获得为止,获得到左右两只筷子随后进行吃,吃完则继续思考,继续循环。
修改:这里为了看到现象,可以将思考时间和吃饭时间设置为0,更加容易出现死锁
还有可以在取左筷子和右筷子间添加一段sleep()时间,虽然不符合正常人(谁会拿了左筷子再sleep然后拿右筷子),但是符合操作系统,因为某个线程获取资源A,会进行一小段时间访问,访问之后才会去访问资源B。
测试类:
package Blog.philosopher;public class PhilosopherTest { public static void main(String[] args) { int size=5; Chopstick[] chopstick = new Chopstick[size]; for(int i=0;i<size;i++){ chopstick[i] = new Chopstick(i); } for(int i=0;i<size;i++){ Thread thread = new Thread(new Philosopher(chopstick[(i+1)%size], chopstick[i], i)); thread.start(); //这里不能用join()方法,因为这会等待该进程结束了才会调用主进程,创造余下的进程 //thread.join(); } System.out.println("main线程已经结束"); }}
结果:出现了死锁
main线程已经结束
Philosopher [index = 0]thinking
Philosopher [index = 1]thinking
Philosopher [index = 0]take the left
Philosopher [index = 2]thinking
Philosopher [index = 1]take the left
Philosopher [index = 3]thinking
Philosopher [index = 4]thinking
Philosopher [index = 2]take the left
Philosopher [index = 3]take the left
Philosopher [index = 4]take the left
Philosopher [index = 0]going to take the right
Philosopher [index = 1]going to take the right
Philosopher [index = 2]going to take the right
Philosopher [index = 4]going to take the right
Philosopher [index = 3]going to take the right
三、解决方法
先看死锁的四个必要条件:
l 互斥
l 请求保持
l 不可剥夺
l 循环等待
要解决死锁,就至少要破坏其中一个条件,然后“互斥”一般是不能破坏的,所以针对其他三个条件采用合适的方法。
1. 资源分级解法:
另一个简单的解法是为资源(这里是餐叉)分配一个偏序或者分级的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,而且保证不会有两个无关资源同时被同一项工作所需要。在哲学家就餐问题中,资源(餐叉)按照某种规则编号为1至5,每一个工作单元(哲学家)总是先拿起左右两边编号较低的餐叉,再拿编号较高的。用完餐叉后,他总是先放下编号较高的餐叉,再放下编号较低的。破坏了循环等待条件。
代码:
继承了Philosopher为Philosopher2,重写了PhilosopherTest为PhilosopherTest2
package Blog.philosopher;public class Philosopher2 extends Philosopher{ //构造方法 public Philosopher2(Chopstick left, Chopstick right, int index) { super(left, right, index); } @Override public void run() { try{ while ( !Thread.interrupted() ){ thinking(); System.out.println(this + "take the small"); Chopstick small = left.getIndex() < right.getIndex() ? left : right; small.take(); Thread.sleep(10); Chopstick big = left.getIndex() > right.getIndex() ? left : right; System.out.println(this + "going to take the big chopstick" + big.getIndex()); big.take(); System.out.println(this + "has took the big right" + big.getIndex()); eating(); big.drop(); small.drop(); } }catch (Exception e){ e.printStackTrace(); } }}
package Blog.philosopher;public class PhilosopherTest2 { public static void main(String[] args) { int size=5; int thinkTime= 0; int eatTime = 0; Chopstick[] chopstick = new Chopstick[size]; for(int i=0;i<size;i++){ chopstick[i] = new Chopstick(i); } for(int i=0;i<size;i++){ Thread thread = new Thread(new Philosopher2(chopstick[(i+1)%size], chopstick[i], i)); thread.start(); //这里不能用join()方法,因为这会等待该进程结束了才会调用主进程,创造余下的进程 //thread.join(); } System.out.println("main线程已经结束"); }}
2. 服务生解法:
一个简单的解法是引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉。因为服务生知道哪只餐叉正在使用,所以他能够作出判断避免死锁。可以采用一次性分配资源的方法来避免死锁。破坏请求保持条件,线程不可能保持资源的同时继续申请新的资源。代码中我没有引入服务生,采用了一种简化,直接在Philosopher3中利用哲学家自己判断,如果右筷子可用则去获取,如果不可用则放弃左筷子。
代码:
重写了Philosopher为Philosopher3,重写了PhilosopherTest为PhilosopherTest3
package Blog.philosopher;public class Philosopher3 extends Philosopher{ public Philosopher3(Chopstick left, Chopstick right, int index) { super(left, right, index); } @Override public void run() { try{ while ( !Thread.interrupted() ){ thinking(); System.out.println(this + "take the left"); left.take(); Thread.sleep(10); System.out.println(this + "going to take the right"); //如果右筷子正在使用,则放弃左筷子 if(right.getIsUsing()){ left.drop(); System.out.println(this + "give up"); }else { right.take(); System.out.println(this + "has took the right"); eating(); left.drop(); right.drop(); } } }catch (Exception e){ e.printStackTrace(); } }}
package Blog.philosopher;public class PhilosopherTest3 { public static void main(String[] args) { int size=5; int thinkTime= 10; int eatTime = 10; Chopstick[] chopstick = new Chopstick[size]; for(int i=0;i<size;i++){ chopstick[i] = new Chopstick(i); } for(int i=0;i<size;i++){ Thread thread = new Thread(new Philosopher3(chopstick[(i+1)%size], chopstick[i], i)); thread.start(); } //提供一个服务生 System.out.println("main线程已经结束"); }}
3. Chandy/Misra解法:
1984年,K. Mani Chandy和J. Misra提出了哲学家就餐问题的另一个解法,允许任意的用户(编号P1, ..., Pn)争用任意数量的资源。与迪科斯彻的解法不同的是,这里编号可以是任意的。
1.对每一对竞争一个资源的哲学家,新拿一个餐叉,给编号较低的哲学家。每只餐叉都是“干净的”或者“脏的”。最初,所有的餐叉都是脏的。
2.当一位哲学家要使用资源(也就是要吃东西)时,他必须从与他竞争的邻居那里得到。对每只他当前没有的餐叉,他都发送一个请求。
3.当拥有餐叉的哲学家收到请求时,如果餐叉是干净的,那么他继续留着,否则就擦干净并交出餐叉。
4.当某个哲学家吃东西后,他的餐叉就变脏了。如果另一个哲学家之前请求过其中的餐叉,那他就擦干净并交出餐叉。
这个解法允许很大的并行性,适用于任意大的问题。
这个方法略复杂,我没有实现,可以参考以下链接
http://bbs.csdn.net/topics/390754385?page=1
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题 锁
- 哲学家就餐问题
- 哲学家就餐问题
- 关于哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 哲学家就餐问题
- 多线程--哲学家就餐问题
- 引用与指针之间的故事
- Collection 线程安全
- 扎心了,老铁!mysql编码问题引发的血案
- Linux环境变量与文件查找
- 银行系统代码
- 哲学家就餐问题
- C#进阶 15 常量,枚举的声明,结构的声明
- 对于python和wamp中mysql数据库的交互
- 简单封装ajax
- 图像处理学习笔记1
- 创建rabbitmq:virtual host、exchange、queue
- Python代码性能优化
- fl2440添加SIM5900B GPRS模块,进行拨号上网,打电话。
- springMVC框架的运行流程