哲学家就餐是经典的PV操作。
一个哲学家同时拿起左边的筷子和右边的筷子进行就餐,每一个哲学家都会等待右边的筷子,具备了死锁问题之一的循环等待。
基础的哲学家就餐问题代码
在Java中,Semaphore
是一个用于控制对某个资源的访问的同步工具。它主要用于管理对有限资源的访问,确保在多线程环境中安全地控制资源的使用。
- 信号量:
Semaphore
是一个计数信号量,维护一个计数器,表示可用资源的数量。 - 获取与释放:线程可以通过
acquire()
方法获取一个许可,当资源可用时,计数器减一;如果没有可用资源,线程会被阻塞。释放资源时,使用release()
方法,计数器加一。 - acquire()就是p操作,获取资源。
- release()就是v操作,用来释放资源。
import java.util.Arrays;
import java.util.concurrent.Semaphore;
public class PhilosopherMeal extends Thread {
private static String name = "哲学家";
private int philosopherNum; //哲学家编号
public static Semaphore[] semaphores = new Semaphore[5];
public PhilosopherMeal(int philosopherNum) {
//调用父类的Thread创建线程
super(PhilosopherMeal.name + String.valueOf(philosopherNum));
this.philosopherNum = philosopherNum;
}
@Override
public void run() {
try {
semaphores[this.philosopherNum].acquire(); //获取锁
Thread.sleep((long) (Math.random() * 1)); //拿起筷子的时间,会发生死锁
//acquire(): 获取一个许可。如果信号量的许可已经被其他线程占用,那么当前线程会阻塞,直到有许可可用。
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].acquire();
System.out.println(this.getName() + "获取到了筷子" + String.valueOf(this.philosopherNum) +
"和" + String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放一个许可,意味着当前线程已不再占用资源,其他线程可以继续使用该资源。
semaphores[(this.philosopherNum)].release();
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].release();
}
}
//哲学家拿起右边的筷子吃饭
private int getRightSemaphoreIndex(int philosopherNum) {
return (philosopherNum - 1 < 0) ? semaphores.length - 1 : philosopherNum - 1;
}
public static void main(String[] args) {
// Arrays.fill(semaphores,new Semaphore(1));
for (int i = 0; i < semaphores.length; i++) {
semaphores[i] = new Semaphore(1);
}
for (int i = 0; i < 5; i++) {
new PhilosopherMeal(i).start();
}
}
}
发生死锁
没有发生死锁
解决方案
1.打破死锁的条件之一 循环条件等待,我们可以破坏循环的条件从而来解决死锁。让奇数的筷子先拿左边的筷子,后拿右边的筷子。
偶数的筷子先拿右边的筷子,后那左边的筷子,这样就可以解决死锁的问题。破快了相互之间循环等待的条件。
@Override
public void run() {
try {
int n = this.philosopherNum; //拿到哲学家的编号
if (n % 2 == 1) { //奇数的先拿左边筷子 偶数拿右边
semaphores[this.philosopherNum].acquire(); //获取锁
Thread.sleep((long) (1000)); //拿起筷子的时间,会发生死锁
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].acquire();
} else {
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].acquire();
Thread.sleep((long) (1000)); //拿起筷子的时间,会发生死锁
semaphores[this.philosopherNum].acquire(); //获取锁
}
//acquire(): 获取一个许可。如果信号量的许可已经被其他线程占用,那么当前线程会阻塞,直到有许可可用。
System.out.println(this.getName() + "获取到了筷子" + String.valueOf(this.philosopherNum) +
"和" + String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放一个许可,意味着当前线程已不再占用资源,其他线程可以继续使用该资源。
semaphores[(this.philosopherNum)].release();
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].release();
}
}
2.最多只能让四个哲学家同时拿起筷子,这样就保证了一定有一个哲学家可以拿到筷子。这样就不会发生死锁。
private static Semaphore count = new Semaphore(4);
@Override
public void run() {
try {
count.acquire(); //获取count
semaphores[this.philosopherNum].acquire(); //获取锁
Thread.sleep(1000);
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].acquire();
System.out.println(this.getName() + "获取到了筷子" + String.valueOf(this.philosopherNum) +
"和" + String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放一个许可,意味着当前线程已不再占用资源,其他线程可以继续使用该资源。
semaphores[(this.philosopherNum)].release();
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].release();
count.release();
}
}
3 拿起左边筷子和右边筷子不是一个原子性的操作,我们可以加一个全局的锁,让拿左边筷子和右边的筷子变为一个原子操作。
private static Semaphore muntex = new Semaphore(1);
@Override
public void run() {
try {
muntex.acquire();
semaphores[this.philosopherNum].acquire(); //获取锁
Thread.sleep((long) (1000)); //拿起筷子的时间,会发生死锁
//acquire(): 获取一个许可。如果信号量的许可已经被其他线程占用,那么当前线程会阻塞,直到有许可可用。
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].acquire();
System.out.println(this.getName() + "获取到了筷子" + String.valueOf(this.philosopherNum) +
"和" + String.valueOf(this.getRightSemaphoreIndex(this.philosopherNum)) + "开始吃饭了");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放一个许可,意味着当前线程已不再占用资源,其他线程可以继续使用该资源。
semaphores[(this.philosopherNum)].release();
semaphores[this.getRightSemaphoreIndex(this.philosopherNum)].release();
muntex.release();
}
}