多线程之死锁,哲学家就餐问题的实现
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的好…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

Linux中《基础IO》详细介绍
目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改,实现简单cat命令 输出信息到显示器,你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...