很喜欢这样的描述如果你喜欢也不防读一读
从许多许多年前,石头就呆在那座岭上了。那是座无名的低岭,毫不起眼,没有足以称道的风景名胜,那块石头只是许多石头中的一颗,见证过日升日落,经历过沧海桑田,承受四季变迁。黄河水数度从它的身上淹没而过,人群在周围来来去去时,放牛的孩子偶尔也在它的身上歇脚。在许久许久的光阴里,它都没有挪动过位置了
可以收获:
对线程间的同步互斥有更深的理解。
对问题的解决如何抽象到具体转为一个具像的代码流程梳理。这很重要!!
例子可以直接跑起来,直接copy!
问题描述:
哲学家就餐问题是1965年由Dijkstra提出的一种线程同步的问题。
问题描述:一圆桌前坐着5位哲学家,两个人中间有一只筷子,桌子中央有面条。哲学家思考问题,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。上述问题会产生死锁的情况,当5个哲学家都拿起自己右手边的筷子,准备拿左手边的筷子时产生死锁现象。
如图:
解决办法:
要么拿起两个筷子,要么不拿起。
思路梳理
指导原则:要么不拿,要么就拿两把叉子
- 思考中
- 进入饥饿状态
- 左右没有邻居进餐进行下一步,否则等待
- 吃面条
- 放下左侧叉子,【如果邻居饿了可以提醒左邻居】
- 放下右侧叉子,【如果邻居饿了可以提醒右邻居】
- 新的循环又开始了,从第一步开始吧!
繁琐啰嗦的步骤抽象成符合人类阅读习惯的可读的api
while(true){
think();
takeForks();
eating();
putForks();
}
先思考,五个哲学家,相当于五个线程,他们要相互通信才能协调,礼让才能让大家都吃到面条。
那么必然有个哲学家的 数据结构,至少包含以下数据结构
通过思路梳理
- 状态类型: 每个哲学家要有三种状态: 思考,饥饿,吃饭饥饿为发起吃面条的请求,就是要拿叉子了。
- 状态数组: 放下叉子的时候要提醒邻居饥饿的邻居吃饭,即要有其他哲学家的状态,所以要有一个状态数组保存个个哲学家状态。这是一个共享的。
- 哲学家编号:由 2 可知数组的索引坐标,要和哲学家做关联映射,表明哪个哲学家对应自己的状态,所以数据结构里有当前哲学家的编号。
- 信号量:而每个哲学家不同状态是可以在三种类型中变化的,所以共享的状态数组要被读写,要加一个锁操作,保证互斥操作。
- 左右节点的引用:要通知左右邻居,则必然持有左右邻居的引用。
- 信号量数组: 要通知邻居,则设计到线程唤醒操作,这里使用信号量,所以每个哲学家要持有一个所有哲学家对应自己的信号量,方便其他人通知自己。
package com.mytest.osStudy;
import lombok.Data;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @ClassName: PhilosopherQuestion
* @Description: os 解决 哲学家就餐问题
* 核心思路:两个叉子要么都拿起来,要么一个不拿
* 核心实现:通过每个哲学家自身的状态,吃饭代表两个叉子都有,用信号量实现 获得许可证代表有左右叉子!!
* @Author fjp
* @Date 2021/7/2-16:56
* @Version 1.0
*/
public class PhilosopherQuestion {
@Data
static
class Philosopher {
// 当前哲学家的编号
private int current;
private Philosopher left;
private Philosopher right;
private int[] state;
private int count;
//这一定是共享的全局,方便唤醒其他人
private Semaphore[] semaphores;
// 为保证对 state[] 的安全读写
private Semaphore mutex;
public Philosopher(int current, int[] states, Semaphore[] semaphores, Semaphore mutext) {
this.current = current;
this.state = states;
this.semaphores = semaphores;
this.mutex = mutext;
}
public void think() throws InterruptedException {
mutex.acquire();
state[current] = 0;
mutex.release();
}
//拿叉子要改变状态,要判断邻居是否没有吃饭才能拿叉子
public void takeForks() throws InterruptedException {
mutex.acquire();
state[current] = 1;
testTakeForks(this); // 能获得叉子的人,会得到一个许可证!!!
mutex.release();
semaphores[current].acquire(); //没有获得叉子的人,就是没有得到许可证的人,会被阻塞!!
}
// index 哲学家的编号
private void testTakeForks(Philosopher p) throws InterruptedException {
//检查左右邻居的状态
String time = new SimpleDateFormat("yyyy-MM-hh HH:mm:sss").format(new Date());
if (state[p.current] == 1 && state[p.left.current] != 2 && state[p.right.current] != 2) {
//状态赋值为2 进入吃饭状态,表示拿到了两个叉子
state[p.current] = 2;
//真正拿到叉子,需要用属于当前哲学家的信号量来约束的,但信号量使用的前提又要靠左右哲学家状态来前提来判断运用,而哲学家的状态又要用mutex 来保证安全
semaphores[p.current].release();
System.out.println(time + "\t我是哲学家p" + current + "\t 第\t" + (++count) + "\t次拿到叉子" + "\t当前所有人状态:\t" + Arrays.toString(state));
} else {
// 两边的邻居有吃饭的,或者自己没有进入 进入饥饿状态的的,一定要记住,饥饿状态才是发起了抢叉子的请求!!!
System.out.println(time + "\t我是哲学家p" + current + "\t 不具备拿叉子条件【我可能是被邻居唤醒检测】\t当前count:" + (count) + "\t当前所有人状态:\t" + Arrays.toString(state));
}
}
private void eating() throws InterruptedException {
//todo eat
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void putForks() throws InterruptedException {
mutex.acquire();
state[current] = 0;//表明放下了叉子
//此时要检查左右邻居
testTakeForks(left);
testTakeForks(right);
mutex.release();
}
public void philospherDay() {
while (true) {
try {
think();
takeForks();
eating();
putForks();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 测试代码
public static void main(String[] args) throws InterruptedException {
// int THINKING = 0; /* 思考*/ int HUNGERY = 1; /*饥饿*/ int EATING = 2; // 吃饭
int count = 5;// 5个哲学家
int[] state = new int[5];
Semaphore[] semaphores = new Semaphore[5]; //索引关联 例如0编号哲学家 则对应semaphores[0]
Semaphore mutex = new Semaphore(1);//
//所以哲学家 最开始都是思考状态
for (int i = 0; i < 5; i++) {
state[i] = 0;
}
// 索引下标对应 哲学家编号,归属哪个哲学家。
for (int i = 0; i < 5; i++) {
semaphores[i] = new Semaphore(0);
}
Philosopher p0 = new Philosopher(0, state, semaphores, mutex);
Philosopher p1 = new Philosopher(1, state, semaphores, mutex);
Philosopher p2 = new Philosopher(2, state, semaphores, mutex);
Philosopher p3 = new Philosopher(3, state, semaphores, mutex);
Philosopher p4 = new Philosopher(4, state, semaphores, mutex);
p0.setLeft(p4);
p0.setRight(p1);
p1.setLeft(p0);
p1.setRight(p2);
p2.setLeft(p1);
p2.setRight(p3);
p3.setLeft(p2);
p3.setRight(p4);
p4.setLeft(p3);
p4.setRight(p0);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(() -> p0.philospherDay());
pool.submit(() -> p1.philospherDay());
pool.submit(() -> p2.philospherDay());
pool.submit(() -> p3.philospherDay());
pool.submit(() -> p4.philospherDay());
Thread.sleep(100000);
}
}
运行结果如下:
2021-07-11 23:58:034 我是哲学家p0 第 1 次拿到叉子 当前所有人状态: [2, 0, 0, 0, 0]
2021-07-11 23:58:034 我是哲学家p1 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:0 当前所有人状态: [2, 1, 0, 0, 0]
2021-07-11 23:58:034 我是哲学家p2 第 1 次拿到叉子 当前所有人状态: [2, 1, 2, 0, 0]
2021-07-11 23:58:034 我是哲学家p3 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:0 当前所有人状态: [2, 1, 2, 1, 0]
2021-07-11 23:58:034 我是哲学家p4 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:0 当前所有人状态: [2, 1, 2, 1, 1]
2021-07-11 23:58:034 我是哲学家p2 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:1 当前所有人状态: [2, 1, 0, 1, 1]
2021-07-11 23:58:034 我是哲学家p2 第 2 次拿到叉子 当前所有人状态: [2, 1, 0, 2, 1]
2021-07-11 23:58:034 我是哲学家p2 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:2 当前所有人状态: [2, 1, 1, 2, 1]
2021-07-11 23:58:034 我是哲学家p0 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:1 当前所有人状态: [0, 1, 1, 2, 1]
2021-07-11 23:58:034 我是哲学家p0 第 2 次拿到叉子 当前所有人状态: [0, 2, 1, 2, 1]
2021-07-11 23:58:034 我是哲学家p0 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:2 当前所有人状态: [1, 2, 1, 2, 1]
2021-07-11 23:58:034 我是哲学家p3 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:0 当前所有人状态: [1, 2, 1, 0, 1]
2021-07-11 23:58:034 我是哲学家p3 第 1 次拿到叉子 当前所有人状态: [1, 2, 1, 0, 2]
2021-07-11 23:58:034 我是哲学家p3 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:1 当前所有人状态: [1, 2, 1, 1, 2]
2021-07-11 23:58:034 我是哲学家p1 不具备拿叉子条件【我可能是被邻居唤醒检测】 当前count:0 当前所有人状态: [1, 0, 1, 1, 2]
2021-07-11 23:58:034 我是哲学家p1 第 1 次拿到叉子 当前所有人状态: [1, 0, 2, 1, 2]
符合预期!!!,不相邻的哲学家可以同时吃饭
赠人玫瑰,手有余香,你的关注,我的动力 ,谢谢!
更多分享可见: