首先我们来了解一下哲学家进餐问题的背景:
话说有5个哲学家围在一张桌子上吃饭,桌上只有5g根筷子,一个要吃饭必须的得有两根筷子,哲学家要吃饭时总是先拿起左边的筷子,在拿起右边的筷子,这样最佳的情况是可同时有两人可以进餐,最坏的情况是大家都拿起了左边的筷子,大家都没得吃。哲学家吃完时会停下来思考一段时间,等饿了在吃。
下面我们来看一下java如何模拟哲学家进餐问题
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
//哲学家吃饭问题
public class ETTest {
//创建大小为5的信号量数组,模拟5根筷子
static Semaphore[] arry=new Semaphore[5];
public static void main(String[] args) {
//创建一个5个线程的线程池
ExecutorService es=Executors.newFixedThreadPool(5);
//初始化信号量
for(int i=0;i<5;i++){
arry[i]=new Semaphore(1,true);
}
//创建5个哲学家 但这样有可能会产生死锁问题
for(int i=0;i<5;i++){
es.execute(new ActionRunnable(i));
}
}
//第i+1号哲学家的活动过程
static class ActionRunnable implements Runnable{
private int i=0;
ActionRunnable(int i){
this.i=i;
}
@Override
public void run() {
while(!Thread.interrupted()){
try {
arry[i].acquire();
//请求右边的筷子
arry[(i+1)%5].acquire();
//吃饭
System.out.println("我是哲学家"+(i+1)+"号我在吃饭");
//释放左手的筷子
arry[i].release();
//释放右手的筷子
arry[(i+1)%5].release();
//哲学家开始思考
System.out.println("我是哲学家"+(i+1)+"号我吃饱了我要开始思考了");
//通知cpu 将调度权让给其他哲学家线程
Thread.yield();
//思考1秒
//把休眠关闭,造成死锁的概率就会增加
//Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
我们在让哲学家吃完后,不进行思考,立即进入下一轮的抢筷子竞争中,以增加死锁出现的概率。
运行上面代码后我们会发现程序死锁了
这样的死锁问题该如何解决了。
有以下俩种解决策略
第一种:
让最多只有四个哲学家可以同时拿起左边的筷子,这样我们就可以保证,最差情况都有至少有一个哲学家可以进餐成功,进餐成功后就会释放筷子资源,这样就不会造成死锁啦。
那java代码是如实现的了。
看下面我基于上面的代码做出的修改。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
//哲学家吃饭问题
public class ETTest2 {
//创建大小为5的信号量数组,模拟5根筷子
static Semaphore[] arry=new Semaphore[5];
//定义一个值为4的信号量,代表最多只能有四个哲学家拿起左边的筷子
static Semaphore leftCount=new Semaphore(4,true);
public static void main(String[] args) {
//创建一个5个线程的线程池
ExecutorService es=Executors.newFixedThreadPool(5);
//初始化信号量
for(int i=0;i<5;i++){
arry[i]=new Semaphore(1,true);
}
//创建5个哲学家 但这样有可能会产生死锁问题
for(int i=0;i<5;i++){
es.execute(new ActionRunnable(i));
}
}
//第i+1号哲学家的活动过程
static class ActionRunnable implements Runnable{
private int i=0;
ActionRunnable(int i){
this.i=i;
}
@Override
public void run() {
while(!Thread.interrupted()){
try {
//看拿起左边筷子的线程数是否已满,可以,则能拿起左边筷子的线程数减一,不能则等待
leftCount.acquire();
arry[i].acquire();
//请求右边的筷子
arry[(i+1)%5].acquire();
//吃饭
System.out.println("我是哲学家"+(i+1)+"号我在吃饭");
//释放左手的筷子
arry[i].release();
//能拿起左边筷子的线程数量加一
leftCount.release();
//释放右手的筷子
arry[(i+1)%5].release();
//哲学家开始思考
System.out.println("我是哲学家"+(i+1)+"号我吃饱了我要开始思考了");
//通知cpu 将调度权让给其他哲学家线程
Thread.yield();
//思考1秒
//把休眠关闭,造成死锁的概率就会增加
//Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
第二种策略为,奇数号的哲学家先拿起左边的筷子,在拿起右边的筷子。偶数号的哲学家先拿起右边的筷子,再拿起左边的筷子,则以就变成,只有1号和2号哲学家会同时竞争1号的筷子,3号和4四号的哲学家会同时竞争3号的筷子,即5位哲学家会先竞争奇数号的筷子,再去竞争偶数号的筷子,最后总会有一个哲学家可以进餐成功。
下面来看一下代码是如何实现的
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
//哲学家进餐问题
public class ETTest {
//创建大小为5的信号量数组,模拟5根筷子
static Semaphore[] arry=new Semaphore[5];
public static void main(String[] args) {
//创建一个5个线程的线程池
ExecutorService es=Executors.newFixedThreadPool(5);
//初始化信号量
for(int i=0;i<5;i++){
arry[i]=new Semaphore(1,true);
}
//创建5个哲学家 但这样有可能会产生死锁问题
for(int i=0;i<5;i++){
es.execute(new ActionRunnable(i));
}
}
//第i+1号哲学家的活动过程
static class ActionRunnable implements Runnable{
private int i=0;
ActionRunnable(int i){
this.i=i;
}
@Override
public void run() {
while(!Thread.interrupted()){
try {
if((i+1)%2!=0){
//奇数号哲学家
//请求左边的筷子
arry[i].acquire();
//请求右边的筷子
arry[(i+1)%5].acquire();
}else{
//偶数号哲学家
//请求右边的筷子
arry[(i+1)%5].acquire();
//再请求左边的筷子
arry[i].acquire();
}
//吃饭
System.out.println("我是哲学家"+(i+1)+"号我在吃饭");
if((i+1)%2!=0){
//奇数号哲学家
//释放左手的筷子
arry[i].release();
//释放右手的筷子
arry[(i+1)%5].release();
}else{
//偶数号的哲学家
arry[(i+1)%5].release();
arry[i].release();
}
//哲学家开始思考
System.out.println("我是哲学家"+(i+1)+"号我吃饱了我要开始思考了");
//通知cpu 将调度权让给其他哲学家线程
Thread.yield();
//思考1秒
//把休眠关闭,造成死锁的概率就会增加
//Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
好了博客写完了,你有没有学到点东西了。