多线程之死锁,哲学家就餐问题的实现
1.死锁是什么
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
2.哲学家就餐问题
有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。

为了进一步阐述死锁的形成, 很多资料上也会谈论到 "哲学家就餐问题".
解决办法一,哲学家要进餐时,要么同时拿起两支筷子,要么一支筷子都不拿.
package thread4;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;//解决办法一,哲学家要进餐时,要么同时拿起两支筷子,要么一支筷子都不拿.
class Philosopher {//哲学家编号public int id;public Philosopher(int id) {this.id = id;}//思考public void thinking() throws InterruptedException {System.out.println("我是哲学家" + this.id + "号,我正在思考!");Thread.sleep(1000);}//吃饭public void eating() throws InterruptedException {System.out.println("我是哲学家" + this.id + "号,我正在吃饭!");Thread.sleep(1000);}//拿筷子public void takeUp(Chopsticks chopsticksLeft, Chopsticks chopsticksRight) throws InterruptedException {synchronized (Test3.locker) {if (chopsticksLeft.used || chopsticksRight.used) {Test3.locker.wait();}}chopsticksLeft.used = true;chopsticksRight.used = true;System.out.println("我是哲学家" + this.id + "号,我拿到俩只筷子!");}//放筷子public void putDown(Chopsticks chopsticksLeft, Chopsticks chopsticksRight) {synchronized (Test3.locker) {chopsticksLeft.used = false;chopsticksRight.used = false;System.out.println("我是哲学家" + this.id + "号,我吃完了!");Test3.locker.notify();}}
}//筷子
class Chopsticks {//筷子编号public int id;//筷子状态public boolean used;public Chopsticks(int id, boolean used) {this.id = id;this.used = used;}
}public class Test3 {public static Object locker = new Object();public static void main(String[] args) {Philosopher philosopher1 = new Philosopher(1);Philosopher philosopher2 = new Philosopher(2);Philosopher philosopher3 = new Philosopher(3);Philosopher philosopher4 = new Philosopher(4);Philosopher philosopher5 = new Philosopher(5);Chopsticks chopsticks1 = new Chopsticks(1, false);Chopsticks chopsticks2 = new Chopsticks(2, false);Chopsticks chopsticks3 = new Chopsticks(3, false);Chopsticks chopsticks4 = new Chopsticks(4, false);Chopsticks chopsticks5 = new Chopsticks(5, false);ExecutorService pool = Executors.newFixedThreadPool(5);pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher1.thinking();philosopher1.takeUp(chopsticks1, chopsticks2);philosopher1.eating();philosopher1.putDown(chopsticks1, chopsticks2);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher2.thinking();philosopher2.takeUp(chopsticks2, chopsticks3);philosopher2.eating();philosopher2.putDown(chopsticks2, chopsticks3);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher3.thinking();philosopher3.takeUp(chopsticks3, chopsticks4);philosopher3.eating();philosopher3.putDown(chopsticks3, chopsticks4);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher4.thinking();philosopher4.takeUp(chopsticks4, chopsticks5);philosopher4.eating();philosopher4.putDown(chopsticks4, chopsticks5);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher5.thinking();philosopher5.takeUp(chopsticks5, chopsticks1);philosopher5.eating();philosopher5.putDown(chopsticks5, chopsticks1);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.shutdown();}
}
解决办法二,给筷子编号,哲学家将要进餐时,要先从小号(左边)的开始拿.
package thread4;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;class Philosopher {//哲学家编号public int id;public Philosopher(int id) {this.id = id;}//思考public void thinking() throws InterruptedException {System.out.println("我是哲学家" + this.id + "号,我正在思考!");Thread.sleep(1000);}//吃饭public void eating() throws InterruptedException {System.out.println("我是哲学家" + this.id + "号,我正在吃饭!");Thread.sleep(1000);}//拿筷子public void takeUp(Chopsticks chopsticksMin, Chopsticks chopsticksMax) throws InterruptedException {synchronized (Test4.locker) {//先尝试拿小号筷子,失败则进入等待状态,并释放锁if (!chopsticksMin.used) {//false能拿,true不能拿别人占用的了所以 是!chopsticksMin.usedchopsticksMin.used = true;if (!chopsticksMax.used) {chopsticksMax.used = true;} else {Test4.locker.wait();}} else {Test4.locker.wait();}}}//放筷子public void putDown(Chopsticks chopsticksMin, Chopsticks chopsticksMax) {synchronized (Test4.locker) {chopsticksMin.used = false;chopsticksMax.used = false;System.out.println("我是哲学家" + this.id + "号,我吃完了!");Test4.locker.notify();}}
}//筷子
class Chopsticks {//筷子编号public int id;//筷子状态public boolean used;public Chopsticks(int id, boolean used) {this.id = id;this.used = used;}
}public class Test4 {public static Object locker = new Object();public static void main(String[] args) {Philosopher philosopher1 = new Philosopher(1);Philosopher philosopher2 = new Philosopher(2);Philosopher philosopher3 = new Philosopher(3);Philosopher philosopher4 = new Philosopher(4);Philosopher philosopher5 = new Philosopher(5);Chopsticks chopsticks1 = new Chopsticks(1, false);Chopsticks chopsticks2 = new Chopsticks(2, false);Chopsticks chopsticks3 = new Chopsticks(3, false);Chopsticks chopsticks4 = new Chopsticks(4, false);Chopsticks chopsticks5 = new Chopsticks(5, false);ExecutorService pool = Executors.newFixedThreadPool(5);pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher1.thinking();philosopher1.takeUp(chopsticks1, chopsticks2);philosopher1.eating();philosopher1.putDown(chopsticks1, chopsticks2);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher2.thinking();philosopher2.takeUp(chopsticks2, chopsticks3);philosopher2.eating();philosopher2.putDown(chopsticks2, chopsticks3);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher3.thinking();philosopher3.takeUp(chopsticks3, chopsticks4);philosopher3.eating();philosopher3.putDown(chopsticks3, chopsticks4);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher4.thinking();philosopher4.takeUp(chopsticks4, chopsticks5);philosopher4.eating();philosopher4.putDown(chopsticks4, chopsticks5);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.submit(new Runnable() {@Overridepublic void run() {try {philosopher5.thinking();philosopher5.takeUp(chopsticks5, chopsticks1);philosopher5.eating();philosopher5.putDown(chopsticks5, chopsticks1);} catch (InterruptedException e) {throw new RuntimeException(e);}}});pool.shutdown();}
}
3.如何避免死锁
死锁产生的四个必要条件
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路
当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。其中最容易破坏的就是 "循环等待".
破坏循环等待
最常用的一种死锁阻止技术就是锁排序. 假设有 N 个线程尝试获取 M 把锁, 就可以针对 M 把锁进行编号(1, 2, 3...M).N 个线程尝试获取锁的时候, 都按照固定的按编号由小到大顺序来获取锁. 这样就可以避免环路等待.
属于理论上的破除死锁的方法.但是这样的方法,并不实用.
更实用的方法,就是尽量避免锁嵌套.(不要在一个加锁的代码中,再去加其他锁...)
可能产生环路等待的代码:
package thread4;public class Test2 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread thread1 = new Thread() {@Overridepublic void run() {synchronized (locker1) {synchronized (locker2) {}}}};thread1.start();Thread thread2 = new Thread() {@Overridepublic void run() {synchronized (locker2) {synchronized (locker1) {}}}};thread2.start();}
}
约定好先获取 lock1, 再获取 lock2 , 就不会环路等待(锁排序)
package thread4;public class Test2 {public static void main(String[] args) {Object locker1 = new Object();Object locker2 = new Object();Thread thread1 = new Thread() {@Overridepublic void run() {synchronized (locker1) {synchronized (locker2) {}}}};thread1.start();Thread thread2 = new Thread() {@Overridepublic void run() {synchronized (locker1) {synchronized (locker2) {}}}};thread2.start();}
}
相关文章:
多线程之死锁,哲学家就餐问题的实现
1.死锁是什么 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 2.哲学家就餐问题 有五个哲学家,他们的生活方式是交替地进行思考和进餐…...
UTF-8编码
介绍 UTF-8 编码 UTF-8 是一种针对 Unicode 的可变长度字符编码。 针对 Unicode:UTF-8 是 Unicode 的实现方式之一。相当于 Unicode 规定了字符对应的代码值,这个代码值需要转换为字节序列的形式,用于数据存储、传输。代码值到字节序列的转…...
likeshop单商户SaaS版V1.8.2说明!
likeshop单商户SaaS版V1.8.2主要更新如下: 新增 前端登录引导用户填写头像昵称 PC端—注册页面显示服务协议和隐私政策 PC端—首次进入商城弹出协议提示 PC端—结算页新增门店自提的配送方式 后台—PC端菜单导航栏的跳转链接支持添加自定义链接 优…...
算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零
算法训练营 day46 动态规划 最后一块石头的重量 II 目标和 一和零 最后一块石头的重量 II 1049. 最后一块石头的重量 II - 力扣(LeetCode) 有一堆石头,用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合…...
nginx-host绕过实例复现
绕过Nginx Host限制第一种处理方法Nginx在处理Host的时候,会将Host用冒号分割成hostname和port,port部分被丢弃。所以,我们可以设置Host的值为2023.mhz.pw:xxx"example.com,这样就能访问到目标Server块:第二种处理…...
Java学习记录day9
类与对象 内部类 成员内部类 在一个类的内部定义的类。 public class Outer {private int a 10;public void outMethod() {System.out.println("这是外部类中的方法");}// 成员内部类public class Inner{private int b 10;public void innerMethod() {// 外部类…...
ActiveReports.NET 17.0 Crack by Xacker
一个完整的报告解决方案,用于在您的业务应用程序中设计、定制、发布和查看报告。 ActiveReports.NET 通过直观的 Visual Studio 集成报表设计器和丰富的控件帮助您提供精美的报表。ActiveReports 提供基于代码的跨平台报告、易于使用的设计器和灵活的 API。适用于桌…...
【计算机网络】传输层TCP协议
文章目录认识TCP协议TCP协议的格式字段的含义序号与确认号六个标志位窗口大小确认应答(ACK)机制超时重传机制连接管理机制三次握手四次挥手滑动窗口流量控制拥塞控制延迟应答捎带应答面向字节流粘包问题TCP异常情况总结认识TCP协议 传输控制协议 (TCP,T…...
Mysql5.7安装【Windows版】
文章目录一、下载二、添加到环境变量三、添加配置文件my.ini四、安装Mysql 修改密码一、下载 下载地址 滑倒最下面有一个MySQL Community Server 选择要下载的版本 二、添加到环境变量 下载好了之后开始解压 把bin目录添加到环境变量 可以点击进入bin目录,直接复…...
分布式一致性算法Raft原理图释
什么是分布式一致性算法Raft 分布式一致性算法Raft:指在分布式场景下实现集群数据同步的解决方案 掌握了这个算法,就可以较容易地处理绝大部分场景的容错和数据一致性需求 Raft三大角色 跟随者(Follower):普通群众…...
网络安全-字典生成-crunch
网络安全-字典生成-crunch crunch工具,在kali已经集成好了 2是代表最小字符长度 4是最大字符长度 生成了一个2M的文件 还有我们来查看这个密码本 从abcd26个英文字母的2位到4位的组合,他全部排列了一次 还可以自定义数字,特殊字符…...
闪光桐人の实习日记
2023年2月13日 1,认识了职场礼仪,学习了职场礼仪的重要性 尊重->心情愉悦->建立信任与好感->合作机遇的敲门砖 2,学习了职场礼仪中的邮件礼仪 模板管理中设置自己的名片 部门写到三级部,如果部门名太长要换一行 发送…...
PostgreSQL 常见配置参数
max_wal_size : 两个检查点(checkpoint)之间,WAL可增长的最大大小,即:自动WAL checkpoint允许WAL增长的最大值。该值缺省是1GB。如果提高该参数值会提升性能,但也是会消耗更多空间、同时会延长崩溃恢复所需…...
JAVA 常用类型之String结构
String在java中我们是用来操作字符串的,但它的底层结构确是一个char[]数组,通过数组的方式将每个字符进行保存。 使用时:String str"ABCD",内部存value确是:value[A,B,C,D]; 如下图: 参考String源…...
二三层网络设备封装与解封装原理
1、寻址转发(寻址指的是寻找IP地址) 路由表放在一个公共的地方,比如主控板上,由主控板 的CPU运行路由协议,计算路由,生成和维护路由表。 转发表与路由表: 转发表是根据路由表生成的。路由表中…...
9、MyBatis框架——使用注解开发实现数据库增删改查操作、一级缓存、二级缓存、MyBatis实现分页
目录 一、使用注解开发实现数据库增删改查操作 1、搭建项目 2、使用注解开发操作数据库 二、一级缓存 1、一级缓存失效的情况 三、二级缓存 1、手动开启二级缓存cacheEnabled 2、二级缓存机制 四、MyBatis实现分页 1、配置环境 2、startPage()开启分页 3、PageInfo…...
C++STL剖析(六)—— set和multiset的概念和使用
文章目录🌟 前言🍑 树型结构和哈希结构🍑 键值对1. set的介绍和使用🍑 set的模板参数列表🍑 set的构造🍑 set的使用🍅 insert🍅 find🍅 erase🍅 swap…...
SpringColud第四讲 Nacos的Windows安装方式和Linux的安装方式
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码: 目录 1.Windows安装Nacos 1.1.下载 1.2.解压 1.3.修改相关配置: 1.4.启动: 1.5.登录: 2.Linux的安装方式Nacos 2.1.…...
微服务项目【网关服务限流熔断降级分布式事务】
网关服务限流熔断降级 第1步:启动sentinel-dashboard控制台和Nacos注册中心服务 第2步:在网关服务中引入sentinel依赖 <!-- sentinel --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-…...
【情人节用Compose给女神写个爱心动画APP】
情人节用Compose给女神写个爱心动画APP前言涉及知识点实现思路实现过程绘制爱心创建动画效果Preview预览效果完整源码彩蛋前言 前一阵子看电视里的学霸用代码写了个炫酷的爱心,网上有很多js和python的源码,复制粘贴就能拥有,但是Android的好…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
