多线程之哲学家就餐问题
1、问题描述
2、死锁
2.1产生死锁的原因
吃饭需要使用两根筷子才可以吃饭,哲学家需要先拿起一边的筷子,再去拿另一边的筷子。假如这个时候每个哲学家都同时拿起了右手边的筷子,去拿左手边筷子的时候,发现左手边的筷子被人拿走了,就需要等待,所有哲学家都在等待左手边的筷子,就造成了死锁。
2.2产生死锁的代码
java是面向对象的语言,所以需要从问题中抽出对象来进行代码的编写。由问题描述中得到两个对象:筷子和哲学家。
Chopstiack.java
/**
* 筷子
*/
public class Chopstick {
private String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
Philosopher .java
哲学家类需要去拿筷子,每个哲学家只负责拿自己的筷子,也就是一个哲学家代表一个线程,所以需要继承Thread类。
/**
* 哲学家类
*/
public class Philosopher extends Thread{
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
synchronized (left) {
synchronized (right) {
eat();
}
}
}
}
private void eat(){
System.out.println(Thread.currentThread().getName() + "eating...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Test.java
测试类
public class TestDeadLock {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
运行结果
查看死锁
下面每一种颜色的框代表着一个死锁,
3、解决办法
3.1 方法
使用ReentrantLock,哲学家首先获取左边的筷子,然后去获取右边的筷子,如果无法获取右边的筷子,就释放左边的筷子。这样就可以打破死锁。
3.2代码
Chopstick.java
/**
* 筷子类
*/
public class Chopstick extends ReentrantLock {
private String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chopstick{" +
"name='" + name + '\'' +
'}';
}
}
Philosopher.java
/**
* 哲学家类
*/
public class Philosopher extends Thread{
private Chopstick left;
private Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
/**
* Chopstick是可重入锁,如果获取了左筷子就可以获取右筷子。
*/
@Override
public void run() {
while (true) {
if(left.tryLock()) {
try {
if(right.tryLock()) {
try {
eat();
}finally {
right.unlock();
}
}
}finally {
left.unlock();
}
}
}
}
public void eat() {
System.out.println(Thread.currentThread().getName() + "eating");
}
}
Test.java
public class Test {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start();
}
}
3.3 执行结果
从下面的图里面可以看出来,没有产生死锁
4、其他解决办法
1、可以给哲学家设置右撇子,即有一个哲学家先去拿右手边的筷子,再去拿左手边的筷子;其他四位哲学家先拿左手边的筷子,再去拿右手边的筷子;
2、根据哲学家的下标进行分类,奇数先拿左边的筷子,再去拿右边的筷子;偶数先去拿右边的筷子,再去拿左边的筷子。