《Java-SE-第三十章》之哲学家就餐问题

简介: 《Java-SE-第三十章》之哲学家就餐问题

文章目录

死锁

哲学家就餐问题

死锁复现

解决办法

死锁

当某个任务在等待另一个任务,而后者又等待别的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁。这得到了一个任务之间互相等待的连续循环, 没有那个线程能继续,这称之为死锁。举个栗子,张三想要回家,当开门的时候,发现没钥匙,此时张三突然想起,屋子钥匙放在了车里面,而车的钥匙在房子里面,想要进入就得打开车子,想要打开车子,必须的进屋子,这就"死锁"了。

示例代码

public class DeadlockDemo {
    public static void main(String[] args) {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                System.out.println("lock A");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (B) {
                    System.out.println("lock B");
                }
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            synchronized (B) {
                System.out.println("lock B");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (A) {
                    System.out.println("lock A");
                }
            }
        });
        t2.start();
    }
}

通过使用jconsole观察两个线程的状态

通过观察可知都死锁了

哲学家就餐问题

 哲学家就餐问题由Edsger Dijkstra提出的,该问题是一个经典的死锁问题,该问题的基本描述中是指定五个哲学家。这些哲学家将花部分时间思考,花部分时间就餐。当他们思考的时候,不需要任何共享资源;但当他们就餐时,将使用有限数量的餐具。在问题的原始描述中,餐具是叉子。要吃到桌子中央盘子里的意大利面条需要用两把叉子,不过把餐具看成是筷子更合理;很明显,哲学家要就餐就需要两根筷子。问题中引入的难点是:作为哲学家,他们很穷,所以他们只能买五根筷子(更一般地讲,筷子和哲学家的数量相同)。他们围坐在桌子周围,每人之间放一根筷子。当一个哲学家要就餐的时候,这个哲学家必须同时得到左边和右边的筷子。上述问题会产生死锁的情况,当5个哲学家都拿起自己左或者右手手边的筷子,准备拿右手或者左手边的筷子时产生死锁现象。

代码演示

  1. 1.定义一个筷子类
public class Chopstick {
    private String name;
    public Chopstick(String name) {
        this.name = name;
    }
}
  1. 2.定义哲学家
public class Philosopher implements Runnable {
    /**
     * 左手筷子
     */
    private Chopstick left;
    /**
     * 右手筷子
     */
    private Chopstick right;
    /**
     * 名字
     */
    private String name;
    public Philosopher(Chopstick left, Chopstick right, String name) {
        this.left = left;
        this.right = right;
        this.name = name;
    }
    public void thinking() {
        System.out.println(this.name + " " + "thinking");
    }
    public void eating() {
        System.out.println(this.name + " " + "eating");
    }
    @Override
    public void run() {
        while (!Thread.interrupted()) {
            thinking();
            synchronized (this.right) {
                synchronized (this.left) {
                    eating();
                }
            }
        }
    }
}

死锁复现

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DeadLockPhilosopher {
    public static void main(String[] args) throws InterruptedException, IOException {
        int size = 5;
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick []  sticks = new Chopstick[size];
        for (int i = 0; i < sticks.length;i++){
            sticks[i] = new Chopstick(i+"号筷子");
        }
        for (int i = 0; i < size;i++) {
            exec.execute(new Philosopher(sticks[i], sticks[(i+1) % size],i+"号哲学家"));
        }
        TimeUnit.SECONDS.sleep(10);
        exec.shutdownNow();
    }
}

运行结果:

代码先是正常运行了一会,然后将就死锁了,是否死锁依旧用jconsole观察,观察结果如下图

解决办法

要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:

1.互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用。

2.请求和保持,至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。

3.资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢Chopstick

4.必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophers.java中,因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发生了循环等待。

 因为要发生死锁的话,所有这些条件必须全部满足;所以要防止死锁的话,只需破坏其中一个即可。在程序中,防止死锁最容易的方法是破坏第4个条件。有这个条件的原因是每个Philosopher都试图用特定的顺序拿Chopstick:先右后左。正因为如此,就可能会发生“每个人都拿着右边的Chopstick,并等待左边的Chopstick”的情况,这就是循环等待条件。然而,如果最后一个Philosopher被初始化成先拿左边的Chopstick,后拿右边的Chopstick,那么这个Philosopher将永远不会阻止其右边的Philosopher拿起他们的Chopstick。

示例代码

import java.util.concurrent.*;
public class FixedDiningPhilosophers {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        Chopstick[] sticks = new Chopstick[5];
        int size = 5;
        for (int i = 0; i < sticks.length; i++) {
            sticks[i] = new Chopstick(i + "号筷子");
        }
        for (int i = 0; i < sticks.length; i++) {
            //前四个哲学家
            if (i < (size - 1)) {
                exec.execute(new Philosopher(sticks[i], sticks[(i + 1)], i + "哲学家"));
            } else {
                exec.execute(new Philosopher(sticks[0], sticks[(i)], i + "哲学家"));
            }
        }
        TimeUnit.SECONDS.sleep(5);
        exec.shutdownNow();
    }
}

运行结果:

由此就解决了哲学家就餐问题中的死锁


各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。

相关文章
|
Java
【技术解码】Java线程的五味人生:新建、就绪、运行、阻塞与死亡的哲学解读!
【6月更文挑战第19天】Java线程生命周期如同人生旅程,经历新建、就绪、运行、阻塞至死亡五阶段。从`new Thread()`的诞生到`start()`的蓄势待发,再到`run()`的全力以赴,线程在代码中奔跑。阻塞时面临挑战,等待资源释放,最终通过`join()`或中断结束生命。线程的每个状态转变,都是编程世界与哲思的交汇点。
97 1
|
2月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
117 0
|
2月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
276 83
|
3月前
|
存储 SQL 安全
Java 无锁方式实现高性能线程实战操作指南
本文深入探讨了现代高并发Java应用中单例模式的实现方式,分析了传统单例(如DCL)的局限性,并提出了多种无锁实现方案。包括基于ThreadLocal的延迟初始化、VarHandle原子操作、Record不可变对象、响应式编程(Reactor)以及CDI依赖注入等实现方式。每种方案均附有代码示例及适用场景,同时通过JMH性能测试对比各实现的优劣。最后,结合实际案例设计了一个高性能配置中心,展示了无锁单例在实际开发中的应用。总结中提出根据场景选择合适的实现方式,并遵循现代单例设计原则以优化性能和安全性。文中还提供了代码获取链接,便于读者实践与学习。
88 0
|
2月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
221 83
|
4月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
180 0
|
3月前
|
存储 Java
说一说 JAVA 内存模型与线程
我是小假 期待与你的下一次相遇 ~
|
3月前
|
移动开发 Java
说一说 Java 是如何实现线程间通信
我是小假 期待与你的下一次相遇 ~

热门文章

最新文章