第16章 Single Thread Execution设计模式(Java高并发编程详解:多线程与系统设计)

server/2025/2/10 7:09:02/

简单来说, Single Thread Execution就是采用排他式的操作保证在同一时刻只能有一个线程访问共享资源。

1.机场过安检

1.1非线程安全

先模拟一个非线程安全的安检口类,旅客(线程)分别手持登机牌和身份证接受工作人员的检查,示例代码如所示。

java">public class FlightSecurity {private int count = 0;// 登机牌private String boardingPass = "null";// 身份证private String idCard = "null";public void pass(String boardingPass, String idCard) {this.boardingPass = boardingPass;this.idCard = idCard;this.count++;}private void check() {// 简单的测试,当登机牌和身份证首字母不相同时则表示检查不通过if ( boardingPass.charAt(0) != idCard.charAt(0)) throw new RuntimeException("======Exception=====" + toString());}@Overridepublic String toString() {return "FlightSecurity{" +"count=" + count +", boardingPass='" + boardingPass + '\'' +", idCard='" + idCard + '\'' +'}';}
}

Flight Security比较简单, 提供了一个pass方法, 将旅客的登机牌和身份证传递给pass方法, 在pass方法中调用check方法对旅客进行检查, 检查的逻辑也足够的简单, 只需要检测登机牌和身份证首字母是否相等(当然这样在现实中非常不合理,但是为了使测试简单我们约定这么做),我们看代码所示的测试.

java">public class FlightSecurityTest {// 旅客线程static class Passengers extends Thread {// 机场安检类private final FlightSecurity flightSecurity;// 旅客的身份证private final String idCard;// 旅客的登机牌private final String boardingPass;// 构造旅客是传入身份证,登机牌以及机场安检类public Passengers(FlightSecurity flightSecurity, String idCard, String boardingPass) {this.flightSecurity = flightSecurity;this.idCard = idCard;this.boardingPass  = boardingPass;}@Overridepublic void run() {while(true) {flightSecurity.pass(boardingPass, idCard);}}}public static void main(String[] args) {// 定义三个旅客,身份证和登机牌首字母均相同final FlightSecurity flightSecurity = new FlightSecurity();new Passengers(flightSecurity, "A1234","AF1234").start();new Passengers(flightSecurity, "B1234", "BF1234").start();new Passengers(flightSecurity,"C1234", "CF1234").start();}
}

首字母相同检查不能通过和首字母不相同检查不能通过,为什么会出现这样的情况呢?首字母相同却不能通过?更加奇怪的是传入的参数明明全都是首字母相同的,为什么会出现首字母不相同的错误呢?

1.2 问题分析

(1)首字母相同却未通过检查

图所示的为首字母相同却无法通过安检的分析过程。

2

(2)为何出现首字母不相同的情况

明明传入的身份证和登机牌首字母都相同,可为何在运行的过程中会出现首字母不相同的情况,下面我们也通过图示的方式进行分析,如图所示。

1.3 线程安全

1.1节中出现的问题说到底就是数据同步的问题, 虽然线程传递给pass方法的两个参数能够百分之百地保证首字母相同, 可是在为FlightSecurity中的属性赋值的时候会出现多个线程交错的情况,结合我们在第一部分第4章的所讲内容可知,需要对共享资源增加同步保护,改进代码如下:

java">    public synchronized void pass(String boardingPass, String idCard) {this.boardingPass = boardingPass;this.idCard = idCard;this.count++;}

何时适合使用single thread execution模式呢?答案如下。

  • 多线程访问资源的时候, 被synchronized同步的方法总是排他性的。
  • 多个线程对某个类的状态发生改变的时候, 比如Flight Security的登机牌以及身份证。

2.吃面问题

2.1吃面引起的死锁

虽然使用synchronized关键字可以保证single thread execution, 但是如果使用不得当则会导致死锁的情况发生,比如A手持刀等待B放下叉,而B手持叉等待A放下刀,示例代码如所示。

java">public class EatNoodleThread extends Thread {private final String name;// 左手边的餐具private final Tableware leftTool;// 右手边的餐具private final Tableware rightTool;public EatNoodleThread(String name, Tableware leftTool, Tableware rightTool) {this.name = name;this.leftTool = leftTool;this.rightTool = rightTool;}@Overridepublic void run() {while (true) {this.eat();}}// 吃面条的过程private void eat() {synchronized (leftTool) {System.out.println(name + " take up" + leftTool + "left");synchronized (rightTool) {System.out.println(name + "take up " + rightTool + "right");}System.out.println(name + " put down " + leftTool);}}public static void main(String[] args) {Tableware fork = new Tableware("fork");Tableware knife = new Tableware("knife");new EatNoodleThread("A", fork, knife).start();new EatNoodleThread("B", knife, fork).start();}
}

2.2 解决吃面引起的死锁问题

为了解决交叉锁的情况,我们需要将刀叉进行封装,使刀叉同属于一个类中,改进代码如所示

java">public class EatNoodleThread1 extends Thread{private final String name;private final TablewarePair tablewarePair;public EatNoodleThread1(String name, TablewarePair tablewarePair) {this.name = name;this.tablewarePair = tablewarePair;}@Overridepublic void run() {while(true) {this.eat();}}private void eat() {synchronized (tablewarePair) {System.out.println("eatting");}}}

2.3哲学家吃面问题

哲学家吃面是解释操作系统中多个进程竞争资源的经典问题,每个哲学家的左右手都有吃面用的刀叉,但是不足以同时去使用,比如A哲学家想要吃面,必须拿起左手边的叉和右手边的刀,但是有可能叉和刀都被其他哲学家拿走使用,或者是手持刀等待别人放下叉等容易引起死锁的问题。


http://www.ppmy.cn/server/166433.html

相关文章

[RabbitMQ] RabbitMQ常见面试题

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

快速搭建 Elasticsearch 8 集群:零基础实战与升级注意事项

引言 随着大数据技术的飞速发展,Elasticsearch 成为许多应用场景中不可或缺的技术,它以其高效的全文搜索引擎和分布式存储架构在企业和个人项目中占据了一席之地。无论是在日志分析、实时搜索还是数据可视化中,Elasticsearch 都发挥着重要的作用。 在这篇文章中,我们将为…

【02】智能合约与虚拟机

Solidity底层 ABI接口详解 ABI是什么? ABI:Application Binary Interface(应用程序二进制接口) 蚂蚁链BaaS平台提供的Cloud IDE,会在合约编译后,一并生成对应的ABI文件(JSON格式描述) ABI…

Sparse4D v3:推进端到端3D检测和跟踪

论文地址:2311.11722 (arxiv.org) 代码地址:HorizonRobotics/Sparse4D (github.com) 在自动驾驶感知系统中,3D 检测和跟踪是两项基本任务。本文在 Sparse4D 框架的基础上更深入地探讨了这一领域。作者引入了两个辅助训练任务(Temp…

操作系统—进程与线程

补充知识 PSW程序状态字寄存器PC程序计数器:存放下一条指令的地址IR指令寄存器:存放当前正在执行的指令通用寄存器:存放其他一些必要信息 进程 进程:进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位…

OpenAI 宣布免费开放 ChatGPT 搜索,无需注册

在科技飞速发展的今天,人工智能领域的每一次突破都犹如一颗重磅炸弹,震撼着整个世界。北京时间 2025 年 2 月 6 日凌晨,OpenAI 宣布向所有用户开放 ChatGPT 搜索功能,且无需注册,这一消息瞬间引发了全球范围内的广泛关…

Enterprise Architect 17 全面升级:重塑建模体验,赋能复杂系统设计

在数字化转型的浪潮中,复杂系统设计已成为企业创新和发展的重要推动力。作为一款广受认可的建模工具,Sparx Systems 的 Enterprise Architect(EA)以其丰富的功能和灵活性,赢得了全球用户的信赖。近日,Sparx…

在CentOS服务器上部署DeepSeek R1

在CentOS服务器上部署DeepSeek R1,并通过公网IP与其进行对话,可以按照以下步骤操作: 一、环境准备 系统要求: CentOS 8+(需支持AVX512指令集)。 硬件配置: GPU版本:NVIDIA驱动520+,CUDA 11.8+。 CPU版本:至少16核处理器,64GB内存。 存储空间:原始模型需要30GB,量…