哲学家就餐问题
如上图,五位哲学家中间间隔放置了五只筷子,只有当哲学家的左手和右手都拿到筷子的时候才能够进餐。当某位哲学家进餐结束以后,会放下手中的筷子给其他哲学家使用。
在某些情况下,会出现所有哲学家都拿到一只筷子,无法进食的情况。并且每个哲学家都不会放弃已经在手中的筷子,这种情况就会一直持续下去,直到哲学家都饿死。这种现象放在计算机线程调度中,就是出现了死锁。
Java代码实现
很明显,每一个筷子可以被当成一个类。
package PhDinner;
public class ChopStick {
}
筷子类比较简单,可以不拥有成员变量及方法。
另外,哲学家类则可以类比为线程,因此可以让它继承线程类。
package PhDinner;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class Philosohper extends Thread{
private ChopStick left;
private ChopStick right;
private int index;
@Override
public void run(){
try {
synchronized (right){ //哲学家先拿右手的筷子
Thread.sleep(2000); //等待2s
synchronized (left){ //再去拿左手的筷子
//拿到筷子打印"eat"
System.out.println(index + "eat");
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
// 测试代码
public static void main(String[] args) {
ChopStick c1 = new ChopStick();
ChopStick c2 = new ChopStick();
ChopStick c3 = new ChopStick();
ChopStick c4 = new ChopStick();
ChopStick c5 = new ChopStick();
Philosohper p1 = new Philosohper(c1,c2,1);
Philosohper p2 = new Philosohper(c2,c3,2);
Philosohper p3 = new Philosohper(c3,c4,3);
Philosohper p4 = new Philosohper(c4,c5,4);
Philosohper p5 = new Philosohper(c5,c1,5);
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
}
}
上面的代码是无法执行出结果的,原因就是每个哲学家都占有了自己右手的筷子,但是左手的筷子被另外一个哲学家占有了,因此无法执行最后的打印语句。
解决方式
我们首先想这个问题的本质,在于每个哲学家都没有拿到左手的筷子,那如果有个哲学家特立独行,他每次先去拿左手的筷子,再去拿右手的筷子呢。这种情况下,假如他拿不到左手的筷子,也不会去拿右手的筷子了,那右边的哲学家就会有两只筷子了。所以就有一位哲学家可以吃饭,那他吃完以后把筷子都释放,其他哲学家也都能吃上饭了。
因此就有了下面的代码:
让某一位哲学家特立独行
@Override
public void run(){
try {
if(index==1){ //第一位哲学家特立独行
synchronized (right){
Thread.sleep(2000);
synchronized (left){
System.out.println(index + "eat");
}
}
}else {
synchronized (left){
Thread.sleep(2000);
synchronized (right){
System.out.println(index + "eat");
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
OK,运行代码,便会出现结果了;
但是这种方式有没有问题呢?还有没有可以优化的点呢,我们仔细思考一下。
问题与优化
在上面的代码中,我们只让一个哲学家特立独行,程序在执行的时候呢,线程也是串行完成的,加入有100万个哲学家的话,串行执行的时间耗费太大。
我们仔细看上面的图,一共有5支筷子,所以一次性可以让2个间隔的哲学家吃上饭。所以我们可以采用奇偶判断的方式:
@Override
public void run(){
try {
if(index%2==0){ //通过奇偶判断位置,采用不同的拿取筷子的方式解开死锁
synchronized (right){
Thread.sleep(2000);
synchronized (left){
System.out.println(index + "eat");
}
}
}else {
synchronized (left){
Thread.sleep(2000);
synchronized (right){
System.out.println(index + "eat");
}
}
}
}
catch (Exception e){
e.printStackTrace();
}
}
至此,我们算是找到了一个解决哲学家进餐问题的较好的方法。