问题描述
有五个哲学家,他们的生活方式是交替地进行思考和进餐,哲学家们共用一张圆桌,分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子,平时哲学家进行思考,饥饿时便试图取其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐,该哲学家进餐完毕后,放下左右两只筷子又继续思考。
约束条件
(1)只有拿到两只筷子时,哲学家才能吃饭。
(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。
(3)任一哲学家在自己未拿到两只筷子吃完饭前,不会放下手中已经拿到的筷子。
问题
用程序模拟该场景,保证哲学家们的动作有序进行,而不会出现哲学家无限等待的情况。
解题思路
五个哲学家对应5个线程,五支筷子对应5个线程执行过程中需要使用的资源。假设五个哲学家分别获得了左边的筷子资源并锁定资源,此时任何一个哲学家都无法再获得右边的筷子资源,这时候就产生了死锁。
只要其中有一个哲学家先获取右边的筷子资源,未获取到第一根筷子资源的哲学家不会去获取第二根筷子资源,就可以解决这个死锁问题。
情况1:获取右边筷子的哲学家未获取到资源,资源被下一个哲学家抢占,此时当前这个哲学家不会去获取左边的筷子资源,上一个哲学家获取到左边筷子资源后就可以获取到右边的筷子资源完成进餐并释放资源。释放的左边筷子资源会被再上一个哲学家获取、完成进餐、释放资源,以此类推,所有哲学家都可以完成进餐。
情况2:获取右边筷子的哲学家获取到资源,他将和抢占左边的筷子资源。抢占成功则完成进餐释放资源,释放资源后上一个哲学家就可以完成进餐释放资源,以此类推所有哲学家都能完成进餐;抢占失败则上一个哲学家就可以完成进餐释放资源,释放资源后再上一个哲学家也可以完成进餐释放资源,以此类推所有哲学家都能完成进餐。
筷子资源类
package com.example.thread.philosopher;
import lombok.Data;
@Data
public class ChopStick {
private String name;
public ChopStick(String name) {
this.name = name;
}
}
哲学家线程类
package com.example.thread.philosopher;
import lombok.SneakyThrows;
public class PhilosopherThread extends Thread {
private String name;
private ChopStick left;
private ChopStick right;
public PhilosopherThread(String name, ChopStick left, ChopStick right) {
this.name = name;
this.left = left;
this.right = right;
}
@SneakyThrows
@Override
public void run() {
synchronized (left) {
System.out.printf("%s获取到资源left:%s,等待资源right:%s",
this.name, this.left.getName(),this.right.getName());
System.out.println();
sleep(1000);
synchronized (right) {
System.out.printf("%s已完成进餐,释放资源left:%s right:%s",
this.name, this.left.getName(), this.right.getName());
System.out.println();
}
}
}
}
模拟死锁问题
package com.example.thread.philosopher;
public class DeadLock {
public void mockDeadLock(){
ChopStick chopStickOne = new ChopStick("chopStickOne");
ChopStick chopStickTwo = new ChopStick("chopStickTwo");
ChopStick chopStickThree = new ChopStick("chopStickThree");
ChopStick chopStickFour = new ChopStick("chopStickFour");
ChopStick chopStickFive = new ChopStick("chopStickFive");
String philosopherOne = "philosopherOne";
String philosopherTwo = "philosopherTwo";
String philosopherThree = "philosopherThree";
String philosopherFour = "philosopherFour";
String philosopherFive = "philosopherFive";
//先获取左侧资源再获取右侧资源
PhilosopherThread philosopherThreadOne
= new PhilosopherThread(philosopherOne,chopStickOne,chopStickTwo);
PhilosopherThread philosopherThreadTwo
= new PhilosopherThread(philosopherTwo,chopStickTwo,chopStickThree);
PhilosopherThread philosopherThreadThree
= new PhilosopherThread(philosopherThree,chopStickThree,chopStickFour);
PhilosopherThread philosopherThreadFour
= new PhilosopherThread(philosopherFour,chopStickFour,chopStickFive);
PhilosopherThread philosopherThreadFive
= new PhilosopherThread(philosopherFive,chopStickFive,chopStickOne);
philosopherThreadOne.start();
philosopherThreadTwo.start();
philosopherThreadThree.start();
philosopherThreadFour.start();
philosopherThreadFive.start();
}
public static void main(String[] args){
new DeadLock().mockDeadLock();
}
}
解决死锁问题
package com.example.thread.philosopher;
public class DeadLockOpen {
public void mockDeadLock(){
ChopStick chopStickOne = new ChopStick("chopStickOne");
ChopStick chopStickTwo = new ChopStick("chopStickTwo");
ChopStick chopStickThree = new ChopStick("chopStickThree");
ChopStick chopStickFour = new ChopStick("chopStickFour");
ChopStick chopStickFive = new ChopStick("chopStickFive");
String philosopherOne = "philosopherOne";
String philosopherTwo = "philosopherTwo";
String philosopherThree = "philosopherThree";
String philosopherFour = "philosopherFour";
String philosopherFive = "philosopherFive";
//先获取左侧资源再获取右侧资源
PhilosopherThread philosopherThreadOne
= new PhilosopherThread(philosopherOne,chopStickOne,chopStickTwo);
PhilosopherThread philosopherThreadTwo
= new PhilosopherThread(philosopherTwo,chopStickTwo,chopStickThree);
PhilosopherThread philosopherThreadThree
= new PhilosopherThread(philosopherThree,chopStickThree,chopStickFour);
PhilosopherThread philosopherThreadFour
= new PhilosopherThread(philosopherFour,chopStickFour,chopStickFive);
//调整为先获取右侧资源再获取左侧资源
PhilosopherThread philosopherThreadFive
= new PhilosopherThread(philosopherFive,chopStickOne,chopStickFive);
philosopherThreadOne.start();
philosopherThreadTwo.start();
philosopherThreadThree.start();
philosopherThreadFour.start();
philosopherThreadFive.start();
}
public static void main(String[] args){
new DeadLockOpen().mockDeadLock();
}
}
该场景下当线程数量和资源数量远大于5个时,可以通过区分奇数偶数位置的哲学家获取筷子资源的顺序来提高多线程执行效率,奇数位哲学家先获取左边的筷子资源,偶数位哲学家先获取右边的筷子资源,来避免出现死锁的问题。