设计题目:哲学家进餐问题
目前累计服务客户近千家,积累了丰富的产品开发及服务经验。以网站设计水平和技术实力,树立企业形象,为客户提供成都网站制作、成都网站建设、外贸营销网站建设、网站策划、网页设计、网络营销、VI设计、网站改版、漏洞修补等服务。创新互联始终以务实、诚信为根本,不断创新和提高建站品质,通过对领先技术的掌握、对创意设计的研究、对客户形象的视觉传递、对应用系统的结合,为客户提供更好的一站式互联网解决方案,携手广大客户,共同发展进步。
一、 设计题目:哲学家进餐问题
二、 设计要求
1、 分析题目
哲学家有N个,也定全体到齐后开始讨论,在讨论的间隙哲学家进餐,(1)每个人只能拿起位于自己两边放着的刀、叉,合成一对才可以用餐;(2)用餐后每人必须将刀、叉放回原处。可以想象,如果每个哲学家都彬彬有礼,并且高谈阔论,轮流吃饭,则这种融洽的气氛可以长久地保持下去。但可能出现这样一种情景:当每个人都拿起自己左手的刀(叉),并且同时去拿自己右手边的叉(刀)时,会发生什么情况:每个人拿着一支餐具,盯着自己右手边的那位哲学家手里的一支餐具,处于僵持状态。这就是发生了“线程死锁”。需要指出的是,线程死锁并不是必然会发生,在某些情况下,可能会非常偶然。
2、 解决方案
一般来说,要出现死锁必须同时据别以下4个条件。因此,只要能够尽可能地破坏这4个条件中的任意一个,就可以避免死锁的出现。(1)互斥条件,即至少存在一个资源,不能被多个线程同时共享。(2)至少存在一个线程,它拥有一个资源,并等待获得另一个线程当前所拥有的资源。(3)线程拥有的资源不能被强行剥夺,只能有线程资源释放。(4)线程对资源的请求形成一个环。
三、 源代码
//DishWare.java
//定义一个DishWareBean进行属性和方法封装
public class DishWare
{
private String name;
public DishWare(String name)
{
this.name=name;
}
public String getNumber()
{
return name;
}
}
//Philospher.java
import java.util.*; //包含常用的数据类型类
public class Philospher extends Thread
{
private DishWare Knife;
private DishWare Fork;
private String name;
private static Random random=new Random();
public Philospher(String name,DishWare Knife,DishWare Fork) //构造函数,变量初始化
{
this.name=name;
this.Knife=Knife;
this.Fork=Fork;
}
public String getNumber()
{
return name;
}
public void run() //多线程的实现方法run()
{
try{
sleep(random.nextInt(3));
}
catch(InterruptedException e){}
synchronized(Knife){ //线程的同步,使用synchronized关键词
System.out.println(this.getNumber()+" has "+Knife.getNumber()+" and wait for "+Fork.getNumber());
synchronized(Fork){
System.out.println(this.getNumber()+" eating");
}
}
}
public static void main(String args[])
{
//建立刀叉对象
DishWare knife1=new DishWare("Knife1");
DishWare fork1=new DishWare("Fork1");
DishWare knife2=new DishWare("Knife2");
DishWare fork2=new DishWare("Fork2");
//建立哲学家对象,并在其两边摆放刀叉
Philospher philospher1=new Philospher("philospher1",knife1,fork1);
Philospher philospher2=new Philospher("philospher2",fork1,knife2);
Philospher philospher3=new Philospher("philospher3",knife2,fork2);
Philospher philospher4=new Philospher("philospher4",fork2,knife1);
//启动3个线程,用start方法开始线程
philospher1.start();
philospher2.start();
philospher3.start();
philospher4.start();
}
}
# define N 5 /* 哲学家数目 */
# define LEFT (i-1+N)%N /* i的左邻号码 */
# define RIGHT (i+1)%N /* i的右邻号码 */
# define THINKING 0 /* 哲学家正在思考 */
# define HUNGRY 1 /* 哲学家想取得叉子 */
# define EATING 2 /* 哲学家正在吃面 */
typedef int semaphore; /* 信号量是一个特殊的整型变量 */
int state[N]; /* 记录每个人状态的数组 */
semaphore mutex=1; /* 临界区互斥 */
semaphore s[N]; /* 每个哲学家一个信号量 */
void philosopher(int i) /* i:哲学家号码,从0到N-1 */
{
while(TRUE)
{ /* 无限循环 */
think(); /* 哲学家正在思考 */
take_forks(i);
/* 需要两只叉子,或者阻塞 */
eat(); /* 进餐 */
put_forks(i);
/* 把两把叉子同时放回桌子 */
}
}
void take_forks(int i) /* i:哲学家号码从0到N-1 */
{
down(mutex); /* 进入临界区 */
state[i]=HUNGRY; /* 记录下哲学家i饥饿的事实 */
test(i); /* 试图得到两只叉子 */
up(mutex); /* 离开临界区 */
down(s[i]); /* 如果得不到叉子就阻塞 */
}
void put_forks(int I) /* i:哲学家号码从0到N-1 */
{
down(mutex); /* 进入临界区 */
state[i]=THINKING; /* 哲学家进餐结束 */
test(LEFT); /* 看一下左邻居现在是否能进餐 */
test(RIGHT); /* 看一下右邻居现在是否能进餐 */
up(mutex); /* 离开临界区 */
}
void test(i) /* i:哲学家号码从0到N-1 */
{
if(state[i]==HUNGRYstate[LEFT]!=EATING
state[RIGHT]!=EATING)
{
state[i]=EATING;
up(s[i]);
}
}
1.哲学家进餐问题:
(1) 在什么情况下5 个哲学家全部吃不上饭考虑两种实现的方式,如下:
A. 算法描述: void philosopher(int i) /*i:哲学家编号,从0 到4*/ {while (TRUE) { think( ); /*哲学家正在思考*/ take_fork(i); /*取左侧的筷子*/ take_fork((i+1) % N); /*取左侧筷子;%为取模运算*/eat( ); /*吃饭*/put_fork(i); /*把左侧筷子放回桌子*/ put_fork((i+1) % N); /*把右侧筷子放回桌子*/ } } 分析:假如所有的哲学家都同时拿起左侧筷子,看到右侧筷子不可用,又都放下左侧筷子,等一会儿,又同时拿起左侧筷子,如此这般,永远重复。对于这种情况,即所有的程序都在无限期地运行,但是都无法取得任何进展,即出现饥饿,所有哲学家都吃不上饭。
B.算法描述:规定在拿到左侧的筷子后,先检查右面的筷子是否可用。如果不可用,则先放下左侧筷子,等一段时间再重复整个过程。分析:当出现以下情形,在某一个瞬间,所有的哲学家都同时启动这个算法,拿起左侧的筷子,而看到右侧筷子不可用,又都放下左侧筷子,等一会儿,又同时拿起左侧筷子……如此这样永远重复下去。对于这种情况,所有的程序都在运行,但却无法取得进展,即出现饥饿,所有的哲学家都吃不上饭。
(2) 描述一种没有人饿死(永远拿不到筷子)算法。考虑了四种实现的方式(A、B、C、D):
A.原理:至多只允许四个哲学家同时进餐,以保证至少有一个哲学家能够进餐,最终总会释放出他所使用过的两支筷子,从而可使更多的哲学家进餐。以下将room 作为信号量,只允许4 个哲学家同时进入餐厅就餐,这样就能保证至少有一个哲学家可以就餐,而申请进入餐厅的哲学家进入room 的等待队列,根据FIFO 的原则,总会进入到餐厅就餐,因此不会出现饿死和死锁的现象。
伪码: semaphore chopstick[5]={1,1,1,1,1}; semaphore room=4; void philosopher(int i) { while(true) {think(); wait(room); //请求进入房间进餐 wait(chopstick[i]); //请求左手边的筷子 wait(chopstick[(i+1)%5]); //请求右手边的筷子 eat(); signal(chopstick[(i+1)%5]); //释放右手边的筷子 signal(chopstick[i]); //释放左手边的筷子 signal(room); //退出房间释放信号量room } }
B.原理:仅当哲学家的左右两支筷子都可用时,才允许他拿起筷子进餐。
方法1:利用AND 型信号量机制实现:根据课程讲述,在一个原语中,将一段代码同时需要的多个临界资源,要么全部分配给它,要么一个都不分配,因此不会出现死锁的情形。当某些资源不够时阻塞调用进程;由于等待队列的存在,使得对资源的请求满足FIFO 的要求,因此不会出现饥饿的情形。
伪码: semaphore chopstick[5]={1,1,1,1,1}; void philosopher(int I) { while(true) { think();Swait(chopstick[(I+1)]%5,chopstick[I]); eat(); Ssignal(chopstick[(I+1)]%5,chopstick[I]);} } 方法2:利用信号量的保护机制实现。通过信号量mutex对eat()之前的取左侧和右侧筷子的操作进行保护,使之成为一个原子操作,这样可以防止死锁的出现。伪码: semaphore mutex = 1 ; semaphorechopstick[5]={1,1,1,1,1};void philosopher(int I) { while(true) { think(); wait(mutex);wait(chopstick[(I+1)]%5); wait(chopstick[I]); signal(mutex); eat();signal(chopstick[(I+1)]%5); signal(chopstick[I]); } }
C.原理:规定奇数号的哲学家先拿起他左边的筷子,然后再去拿他右边的筷子;而偶数号的哲学家则相反.按此规定,将是1,2号哲学家竞争1号筷子,3,4号哲学家竞争3号筷子.即五个哲学家都竞争奇数号筷子,获得后,再去竞争偶数号筷子,最后总会有一个哲学家能获得两支筷子而进餐。而申请不到的哲学家进入阻塞等待队列,根FIFO原则,则先申请的哲学家会较先可以吃饭,因此不会出现饿死的哲学家。
伪码: semaphore chopstick[5]={1,1,1,1,1}; void philosopher(int i) { while(true) { think(); if(i%2== 0) //偶数哲学家,先右后左。 { wait (chopstick[ i + 1 ] mod 5) ; wait (chopstick[ i]) ;eat(); signal (chopstick[ i + 1 ] mod 5) ; signal (chopstick[ i]) ; } Else //奇数哲学家,先左后右。 { wait (chopstick[ i]) ; wait (chopstick[ i +1 ] mod 5) ; eat(); signal (chopstick[ i]) ; signal (chopstick[ i + 1 ] mod 5); } }
D.利用管程机制实现(最终该实现是失败的,见以下分析):原理:不是对每只筷子设置信号量,而是对每个哲学家设置信号量。test()函数有以下作用:
a. 如果当前处理的哲学家处于饥饿状态且两侧哲学家不在吃饭状态,则当前哲学家通过 test()函数试图进入吃饭状态。
b. 如果通过test()进入吃饭状态不成功,那么当前哲学家就在该信号量阻塞等待,直到其他的哲学家进程通过test()将该哲学家的状态设置为EATING。
c. 当一个哲学家进程调用put_forks()放下筷子的时候,会通过test()测试它的邻居,如果邻居处于饥饿状态,且该邻居的邻居不在吃饭状态,则该邻居进入吃饭状态。由上所述,该算法不会出现死锁,因为一个哲学家只有在两个邻座都不在进餐时,才允许转换到进餐状态。该算法会出现某个哲学家适终无法吃饭的情况,即当该哲学家的左右两个哲学家交替处在吃饭的状态的时候,则该哲学家始终无法进入吃饭的状态,因此不满足题目的要求。但是该算法能够实现对于任意多位哲学家的情况都能获得最大的并行度,因此具有重要的意义。
伪码:#define N 5 /* 哲学家人数*/#define LEFT (i-1+N)%N /* i的左邻号码 */ #define RIGHT (i+1)%N /* i的右邻号码 */ typedef enum { THINKING, HUNGRY, EATING } phil_state; /*哲学家状态*/ monitor dp /*管程*/ { phil_state state[N]; semaphore mutex =1; semaphore s[N];/*每个哲学家一个信号量,初始值为0*/void test(int i) { if ( state[i] == HUNGRY state[LEFT(i)] != EATING state[RIGHT(i)] != EATING ) { state[i] = EATING; V(s[i]); } } void get_forks(inti) { P(mutex); state[i] = HUNGRY; test(i); /*试图得到两支筷子*/ V(mutex); P(s[i]); /*得不到筷子则阻塞*/ } void put_forks(int i) { P(mutex); state[i]= THINKING;test(LEFT(i)); /*看左邻是否进餐*/ test(RIGHT(i)); /*看右邻是否进餐*/ V(mutex); } } 哲学家进程如下: void philosopher(int process) { while(true) { think();get_forks(process); eat(); put_forks(process); } }
2.理发师问题:一个理发店有一个入口和一个出口。理发店内有一个可站5 位顾客的站席区、4 个单人沙发、3 个理发师及其专用理发工具、一个收银台。新来的顾客坐在沙发上等待;没有空沙发时,可在站席区等待;站席区满时,只能在入口外等待。理发师可从事理发、收银和休息三种活动。
理发店的活动满足下列条件:
1)休息的理发师是坐地自己专用的理发椅上,不会占用顾客的沙发;
2)处理休息状态的理发师可为在沙发上等待时间最长的顾客理发;
3)理发时间长短由理发师决定;
4)在站席区等待时间最长的顾客可坐到空闲的理发上;
5)任何时刻最多只能有一个理发师在收银。试用信号量机制或管程机制实现理发师进程和顾客进程。
原理:
(1) customer 进程:首先检查站席区是否已满(stand_capacity),若满选择离开,否则进入站席区,即进入理发店。在站席区等待沙发的空位(信号量sofa),如果沙发已满,则进入阻塞等待队列,直到出现空位,在站席区中等待时间最长的顾客离开站席区(stand_capacity)。坐到沙发上,等待理发椅(barber_chair),如果理发椅已满,则进入阻塞等待队列,直到出现空位,在沙发上等待时间最长的顾客离开沙发(释放信号量sofa)。坐到理发椅上,释放准备好的信号(customer_ready),获得该理发师的编号(0~1 的数字)。等待理发师理发结束(finished[barber_number])。在离开理发椅之前付款(payment),等待收据 (receipt),离开理发椅(leave_barberchair)。最后离开理发店。
这里需要注意几点:
a) 首先是几个需要进行互斥处理的地方,主要包括:进入站席区、进入沙发、进入理发椅和付款几个地方。
b) 通过barber_chair保证一个理发椅上最多只有一名顾客。但这也不够,因为单凭 baber_chair 无法保证一名顾客离开理发椅之前,另一位顾客不会坐到该理发椅上,因此增加信号量leave_barberchair,让顾客离开理发椅后,释放该信号,而理发师接收到该信号后才释放barber_chair 等待下一位顾客。
c) 在理发的过程中,需要保证是自己理发完毕,才能够进行下面的付款、离开理发椅的活动。这个机制是通过customer 进程获得给他理发的理发师编号来实现的,这样,当该编号的理发师释放对应的finished[i]信号的时候,该顾客才理发完毕。
d) 理发师是通过mutex 信号量保证他们每个人同时只进行一项操作(理发或者收款)。
e) 为了保证该顾客理发完毕后马上可以付款离开,就应该保证给该顾客理发的理发师在理发完毕后马上到收银台进入收款操作而不是给下一位顾客服务。在伪码中由以下机制实现:即顾客在释放离开理发椅的信号前,发出付款的信号。这样该理发师得不到顾客的离开理发椅的信号,不能进入下一个循环为下一名顾客服务,而只能进入收款台的收款操作。直到顾客接到收据后,才释放离开理发椅的信号,离开理发椅,让理发师释放该理发椅的信号,让下一位等待的顾客坐到理发椅上。
(2)barber 进程首先将该理发师的编号压入队列,供顾客提取。等待顾客坐到理发椅坐好(信号量 customer_ready),开始理发,理发结束后释放结束信号(finished[i])。等待顾客离开理发椅(leave_barberchair)(期间去收银台进行收款活动),释放理发椅空闲信号(barber_chair),等待下一位顾客坐上来。
(3)cash(收银台)进程等待顾客付款(payment),执行收款操作,收款操作结束,给付收据(receipt)。信号量总表:信号量 waitsignal stand_capacity 顾客等待进入理发店顾客离开站席区 sofa 顾客等待坐到沙发顾客离开沙发 barber_chair 顾客等待空理发椅理发师释放空理发椅 customer_ready 理发师等待,直到一个顾客坐到理发椅顾客坐到理发椅上,给理发师发出信号 mutex 等待理发师空闲,执行理发或收款操作理发师执行理发或收款结束,进入空闲状态 mutex1执行入队或出队等待入队或出队结束,释放信号 finished[i] 顾客等待对应编号理发师理发结束理发师理发结束,释放信号 leave_barberchair 理发师等待顾客离开理发椅顾客付款完毕得到收据,离开理发椅释放信号 payment 收银员等待顾客付款顾客付款,发出信号 receipt 顾客等待收银员收、开具收据收银员收款结束、开具收据,释放信号
伪码: semaphore stand_capacity=5; semaphore sofa=4; semaphorebarber_chair=3; semaphore customer_ready=0; semaphore mutex=3; semaphoremutex1=1; semaphore finished[3]={0,0,0}; semaphore leave_barberchair=0;semaphore payment=0; semaphore receipt=0; void customer() { int barber_number;wait(stand_capacity); //等待进入理发店 enter_room(); //进入理发店 wait(sofa); //等待沙发 leave_stand_section(); //离开站席区 signal(stand_capacity); sit_on_sofa(); //坐在沙发上 wait(barber_chair); //等待理发椅 get_up_sofa(); //离开沙发 signal(sofa); wait(mutex1);sit_on_barberchair(); //坐到理发椅上 signal(customer_ready); barber_number=dequeue(); //得到理发师编号 signal(mutex1); wait(finished[barber_number]);//等待理发结束 pay(); //付款 signal(payment); //付款 wait(receipt); //等待收据 get_up_barberchair(); //离开理发椅 signal(leave_barberchair); //发出离开理发椅信号 exit_shop(); //了离开理发店 } void barber(int i) { while(true) { wait(mutex1);enqueue(i); //将该理发师的编号加入队列 signal(mutex1); wait(customer_ready); //等待顾客准备好 wait(mutex); cut_hair(); //理发 signal(mutex); signal(finished[i]); //理发结束 wait(leave_barberchair); //等待顾客离开理发椅信号 signal(barber_chair); //释放barber_chair 信号 } } void cash() //收银 { while(true) { wait(payment); //等待顾客付款 wait(mutex); //原子操作 get_pay(); //接受付款 give_receipt(); //给顾客收据 signal(mutex); signal(receipt); //收银完毕,释放信号 } }
分析:在分析该问题过程中,出现若干问题,是参阅相关资料后才认识到这些问题的隐蔽性和严重性的,主要包括:
(1)在顾客进程,如果是在释放leave_barberchair 信号之后进行付款动作的话,很容易造成没有收银员为其收款的情形,原因是:为该顾客理发的理发师收到 leave_barberchair 信号后,释放barber_chair 信号,另外一名顾客坐到理发椅上,该理发师有可能为这另外一名顾客理发,而没有为刚理完发的顾客收款。为解决这个问题,就是采取在释放leave_barberchair 信号之前,完成付款操作。这样该理发师无法进入下一轮循环为另外顾客服务,只能到收银台收款。
(2)本算法是通过给理发师编号的方式,当顾客坐到某理发椅上也同时获得理发师的编号,如此,当该理发师理发结束,释放信号,顾客只有接收到为其理发的理发师的理发结束信号才会进行付款等操作。这样实现,是为避免这样的错误,即:如果仅用一个finished 信号量的话,很容易出现别的理发师理发完毕释放了finished 信号,把正在理发的这位顾客赶去付款,而已经理完发的顾客却被阻塞在理发椅上的情形。当然也可以为顾客进行编号,让理发师获取他理发的顾客的编号,但这样就会限制顾客的数量,因为finished[] 数组不能是无限的。而为理发师编号,则只需要三个元素即可。
public class Scientist extends Thread {
/*
* @author Bore
* @mail z
* @time / /
* 哲学家进餐 个哲学家 只筷子
* 哲学家通过getChopsticks()方法 来取筷子
* 当哲学家同时获得左边和右边两只筷子后才可以进餐 否则把已经得到的筷子也释放
* 如果一直筷子也没有 进程阻塞 等待其他哲学家释放资源
* 进程后 释放已经获得的资源
*/
static byte[] chopsticks={ };//五只筷子
public Scientist(String name){
super(name);
}
public void run() {
getChopsticks();
}
public void getChopsticks(){//开始抢筷子
int tem=Integer parseInt(this getName() substring( ));//获取哲学家编号
if(tem== ){//如果是第一位科学家 先抢左边的筷子
if(leftChopsticks(tem)){//如何获取了左边的筷子
if(rightChopsticks(tem)){//如果获取了右边的筷子
eating();//开始吃饭
freeLeftChopsticks(tem);//释放左边筷子
freeRightChopsticks(tem);//释放右边筷子
}else{
freeLeftChopsticks(tem);
System out println( 由于 +this getName()+ 无法获得右手边的筷子 所以他把已获得的左手的筷子也释放了! );
try {
this sleep( );
} catch (InterruptedException e) {
e printStackTrace();
}
getChopsticks();
}
}else{
System out println(this getName()+ 暂时无法获取两只筷子 准备休眠! );
try {
this sleep( );
} catch (InterruptedException e) {
e printStackTrace();
}
getChopsticks();
}
}else{//其他情况先抢右边的筷子
if(rightChopsticks(tem)){//先抢右手边的筷子
if(leftChopsticks(tem)){//如果获得了右手边的 去抢左手边的筷子
eating();//如果获得了两只筷子 开始吃饭
freeLeftChopsticks(tem);//吃完了 释放左手边的筷子
freeRightChopsticks(tem);//释放右手边的筷子
}else{
freeRightChopsticks(tem);
System out println( 由于 +this getName()+ 无法获得左手边的筷子 所以他把已获得的右手的筷子也释放了! );
try {
this sleep( );
} catch (InterruptedException e) {
e printStackTrace();
}
getChopsticks();
}
}else{
System out println(this getName()+ 暂时无法获取两只筷子 准备休眠! );
try {
this sleep( );
} catch (InterruptedException e) {
e printStackTrace();
}
getChopsticks();
}
}
}
public boolean leftChopsticks(int tem){//获取左手边筷子
if(chopsticks[tem]== ){
chopsticks[tem]= ;
System out println(this getName()+ 左手边筷子已获得! );
return true;
}else{
System out println(this getName()+ 左手边筷子已被哲学家 +(tem )+ 抢走! );
return false;
}
}
public boolean rightChopsticks(int tem){//获取右手边筷子
int i=(tem+ )% ;
if(chopsticks[i]== ){
chopsticks[i]= ;
System out println(this getName()+ 右手边筷子已获得! );
return true;
}else{
System out println(this getName()+ 右手边筷子已被哲学家 +i+ 抢走! );
return false;
}
}
public void freeLeftChopsticks(int tem){//获取左手边筷子
chopsticks[tem]= ;
System out println(this getName()+ 左手边筷子已释放! );
}
public void freeRightChopsticks(int tem){//获取右手边筷子
int i=(tem+ )% ;
chopsticks[i]= ;
System out println(this getName()+ 右手边筷子已释放! );
}
public void eating(){//开始进餐
System out println( * +this getName()+ 两只手都有了筷子 所以开始吃饭! );
}
/**
* 主函数
*/
public static void main(String[] args) {
for(int i= ; i ; i++){
new Scientist( 哲学家 +i) start();
}
}
lishixinzhi/Article/program/Java/hx/201311/25798
售后响应及时
7×24小时客服热线数据备份
更安全、更高效、更稳定价格公道精准
项目经理精准报价不弄虚作假合作无风险
重合同讲信誉,无效全额退款