哲学家就餐问题
问题描述
哲学家就餐问题是经典的死锁问题,最近在学习多线程,所以就拿这个问题来练手。哲学家就餐问题描述为:有五个哲学家坐在一个圆桌上,哲学家就做两个事情,一是吃饭,另一个就是思考。吃饭的时候,以为哲学家需要同时拿着自己手边的两把叉子才可以就餐,否则就不能就餐。思考的时候就放下叉子。
这个问题抽象出来,就是有五个线程,彼此两个线程之间争夺同一个资源,同时获得两个资源才能运行。死锁问题在于当五个线程各拿到一个资源的时候,这个时候都在等待另一个资源,不释放手中的资源,所以产生死锁。
解决方法
错误解法
在网上看到一个错误解法,设置哲学家每次拿起叉子的时候就判断是否能获得另一个叉子,如果不能就放下叉子。这个解法有一个很大的问题在于,如果五个哲学家同时拿起一个叉子进行判断发现无法获得另一个叉子,就放下手中的叉子。抑制这样循环下去,从而产生“活锁”。
正确解法
一般解法有两个:一是服务生解法,二是资源分级。本文解法采用服务生解法,只是没有模拟服务生。除非同时获得两个资源,否则不获取资源。
代码
talk is cheap, show me the code
Fork.java
package com.edu.philosopher;
/**
* 叉子类,模拟被争夺资源
*/
public class Fork {
/**
* 表示叉子是否可用
*/
private volatile boolean isAvailable = true;
/**
* 叉子ID
*/
private int id;
public Fork(int id){
this.id = id;
}
public boolean isAvailable() {
return isAvailable;
}
public void setAvailable(boolean isAvailable) {
this.isAvailable = isAvailable;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "叉子" + id;
}
}
package com.edu.philosopher;
/**
* 叉子数组,模拟多个资源
*/
public class ForkArray {
private Fork[] forkArray;
/**
* 创建动态数组
*/
public ForkArray(int size){
forkArray = new Fork[size];
for (int i = 0; i < forkArray.length; i++) {
forkArray[i] = new Fork(i);
}
}
/**
* 获取其中一只叉子
*/
public Fork getFork(int id){
return forkArray[id];
}
/**
* 获取上一直叉子左侧的叉子
*/
public Fork getTheLeftFork(int id){
if(id == 0){
return forkArray[forkArray.length - 1];
} else {
return forkArray[id - 1];
}
}
}
package com.edu.philosopher;
import java.util.Random;
/**
* 哲学家类
*/
public class Philosopher extends Thread {
/**
* 哲学家ID
*/
private int id;
/**
* 是否在用餐,true表示在用餐,false表示在思考
*/
private boolean status;
/**
* 叉子数组,资源队列
*/
private ForkArray forkArray;
public Philosopher(int id, ForkArray forkArray){
this.id = id;
this.forkArray = forkArray;
}
public synchronized void eating(){
if(!status){
if(forkArray.getFork(id).isAvailable()){
if(forkArray.getTheLeftFork(id).isAvailable()){
//该哲学家左右两边的叉子都可用,开始用餐,锁定叉子状态
forkArray.getFork(id).setAvailable(false);
forkArray.getTheLeftFork(id).setAvailable(false);
System.out.println(this + "在吃饭!");
//模拟用餐过程,占用一定时间的资源
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
} else {
//左边叉子不可用,等待资源
System.out.println(this + "在等待" + forkArray.getFork(id));
try {
wait(new Random().nextInt(10));
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
//右边叉子不可用,等待资源
System.out.println(this + "在等待" + forkArray.getTheLeftFork(id));
try {
wait(new Random().nextInt(10));
} catch (Exception e) {
e.printStackTrace();
}
}
}
status = true;
}
public synchronized void thinking(){
if(status){
//该哲学家处于思考状态,释放两边叉子资源
forkArray.getFork(id).setAvailable(true);
forkArray.getTheLeftFork(id).setAvailable(true);
System.out.println(this + "在思考!");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
status = false;
}
@Override
public void run() {
//10组测试
for(int i = 0; i < 10; i++){
eating();
thinking();
}
}
@Override
public String toString() {
return "哲学家" + id;
}
}
测试代码如下:
package com.edu.philosopher;
import java.util.Random;
public class MainTester {
public static void main(String[] args) {
ForkArray forkArray = new ForkArray(5);
for(int i = 0; i < 5; i++){
new Thread(new Philosopher(i, forkArray)).start();
}
}
}
错误调试
代码运行服务器报错:
后来查询报错位置在wait()方法,之前我写的是wait(new Random().nextInt());在网上查询,网上说nextInt()会返回负数,测试代码如下:
for(int i = 0; i < 100; i++){
System.out.println(new Random().nextInt());
}
运行结果如下:
修改wait()方法,wait(new Random().nextInt(10));new Random().nextInt(10)返回0~9之间的随机数。
参考文章
一家之言,欢迎拍砖