哲学家就餐问题

五个哲学家,只做两件事,思考和吃饭。
吃饭要用到两根筷子,每个哲学家左手边和右手边各有一根筷子,而桌上只有五根,当筷子被旁边的人拿着,自己就只能进入等待
筷子类
class Chopstick {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
哲学家类
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
@Override
public void run() {
while (true) {
// 获得左手筷子
synchronized (left) {
// 获得右手筷子
synchronized (right) {
// 吃饭
eat();
}
// 放下右手筷子
}
// 放下左手筷子
}
}
}
就餐(执行)
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();
执行后,用jconsole检测死锁发现五位哲学家都进入了死锁状态
ReentrantLock
可重入锁,顾名思义,支持可重入
- 可中断
- 可设置超时时间
- 可设置为公平锁(先进先出,处理饥饿,但是一般不使用)
- 支持多个条件变量(Condition)
基本语法

- 可重入指线程1首次获得了锁A,因为线程1是锁A的拥有者,所有有权利再次获取这把锁
- 不可重入锁,第二次获得锁时,自己也会被锁挡住
@Slf4j
public class Reentrant {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1(){
// 获取锁
lock.lock();
try {
// 临界区
log.info("execute method1");
method2();
}finally {
// 释放锁
lock.unlock();
}
}
public static void method2(){
// 获取锁
lock.lock();
try {
// 临界区
log.info("execute method2");
}finally {
// 释放锁
lock.unlock();
}
}
}

可打断
lock.lockInterruptibly(),可以防止线程一直等待锁
@Slf4j
public class Reentrant {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.info("start...");
try {
// 判断锁是否被打断,打断了抛出异常
// 没有打断则get lock...执行完后释放锁
lock.lockInterruptibly();
}catch (InterruptedException e){
e.printStackTrace();
log.info("waiting is interrupt...");
return;
}
try {
log.info("get lock....");
}finally {
// 释放锁
lock.unlock();
}
},"t1");
lock.lock();
log.info("get lock ....");
t1.start();
try {
Sleepy.sleep(1000);
t1.interrupt();
log.info("interrupt execute");
}finally {
lock.unlock();
}
}
}
主线程获取锁后,执行t1线程,此时t1线程被打断,抛出异常后跳出循环

如果是不可中断模式,即使使用了interrupt也不会让等待中断
@Slf4j
public class Reentrant {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.info("start...");
lock.lock();
try {
log.info("get lock....");
}finally {
// 释放锁
lock.unlock();
}
},"t1");
lock.lock();
log.info("get lock ....");
t1.start();
try {
Sleepy.sleep(1000);
t1.interrupt();
log.info("interrupt execute");
}finally {
lock.unlock();
}
}
}
t1不会因为interrupt而获取不到锁
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lz4LaEAE-1655570755988)(D:\Note\面试学习笔记.assets\image-20220619001322326.png)]](https://i-blog.csdnimg.cn/blog_migrate/4669fe5c2f7f6c1abbe75d3042461eda.png)
锁超时
立刻失败
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.info("start.....");
// 获取锁失败时立刻失败
if(!lock.tryLock()){
log.info("fail to get the lock.....");
return;
}
try {
log.info("get lock...");
}finally {
lock.unlock();
}
},"t1");
lock.lock();
t1.start();
try {
Sleepy.sleep(1000);
}finally {
lock.unlock();
}
}

超时失败
t1在1s后,因为主线程释放了锁,所以获取锁成功
public static void main(String[] args) {
Thread t1 = new Thread(()->{
log.info("start.....");
// 获取锁失败时等待1秒
try {
if(!lock.tryLock(1, TimeUnit.SECONDS)){
log.info("fail to get the lock.....");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.info("get lock...");
}finally {
lock.unlock();
}
},"t1");
lock.lock();
t1.start();
try {
Sleepy.sleep(1000);
}finally {
lock.unlock();
}
}

使用tryLock解决哲学家问题
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
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) {
// 尝试获得左手筷子
if (left.tryLock()) {
try {
// 尝试获得右手筷子
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
}
条件变量
Synchronized也有条件变量,在monitor中的waitSet,当条件不满足时进入waitSet等待
ReentrantLock的条件变量可以有多个
使用要点
- await前需要获取锁
- await执行后会释放锁,进入条件变量对象(conditionObject)等待
- await的线程==被唤醒(打断/超时)==去重新竞争lock锁
- 竞争lock锁成功后,从await后继续执行
通过公车和汽车两个条件,创建对应的条件变量
--》当还没有车启动时,进入等待状态,此时获得了锁,在此期间一直等待车子发动
--》当车发动后,条件变量收到通知,开始上车操作
@Slf4j
public class MutilCondition {
static ReentrantLock lock = new ReentrantLock();
static Condition waitCar = lock.newCondition();
static Condition waitBus = lock.newCondition();
static volatile boolean hasCar = false;
static volatile boolean hasBus = false;
public static void main(String[] args) {
new Thread(()->{
try {
lock.lock();
while(!hasCar){
try {
waitCar.await();
}catch (InterruptedException e){
e.printStackTrace();
}
}
log.info("car coming and get...");
}finally {
// 等到汽车后释放锁
lock.unlock();
}
}).start();
new Thread(()->{
try {
lock.lock();
while (!hasBus){
try {
waitBus.await();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("bus coming and get...");
}finally {
lock.unlock();
}
}).start();
Sleepy.sleep(1000);
startCar();
Sleepy.sleep(1000);
startBus();
}
private static void startCar(){
lock.lock();
try {
log.info("car is starting...");
hasCar = true;
// 等汽车的条件变量收到通知
waitCar.signal();
}finally {
lock.unlock();
}
}
private static void startBus(){
lock.lock();
try {
log.info("bus is starting...");
hasBus = true;
// 等公交的条件变量收到通知
waitBus.signal();
}finally {
lock.unlock();
}
}
}

本文探讨了五个哲学家就餐问题,通过Java ReentrantLock实现避免死锁的解决方案。介绍可重入锁、可中断、超时和条件变量在解决哲学家困境中的应用,展示了如何使用tryLock方法和条件变量来确保并发安全。
1119

被折叠的 条评论
为什么被折叠?



