1-Single Thread

news/2025/2/4 5:44:49/

单线程执行模式

案例-1

背景

模拟3个人频繁地经过同一个只能容许一个人经过的门 。

(模拟三个线程调用同一个对象的方法)

当人通过门的时候,这个程序会在计数器中,递增通过的人数。另外, 还会记录通过的人的 “ 姓名与出生地”。

(当某一个线程调用该对象方法的时候,都会进行计数(使该对象的counter属性+1),另外还会记录当前对象的name和address属性,并检查name和address属性关系是否正确对应)

Main.class

​ 创建一 个门,并操作了个人不断地穿越门的 类

Gate.class

​ 表 示 门 的 类 , 当 人经 过 时 会 记 录姓 名 与 出 生 地 表示人的类,

UserThread.class

​ 只负责处理不断地在门间穿梭通过

Gate.class
class Gate{//表示已经通过这道门的人数private int counter = 0;//表示通过这道门的人名private String name = "Nobody";//表示通过这道门的人的出生地private String address = "Nowhere";public void pass (String name, String address) {this.counter++;this.name = name;this.address = address;check();}public String toString() {return "No." + counter + ": " + name + ", " + address;}/*** 这里为了判断人名和地址是否匹配直接看 人名 和 出生地 的首字母是否一致进行判断了*/private void check(){if (name.charAt(0) != name.charAt(0)){System.out.println("********* broken *********** " + toString());}}
}
UserThread.class
class UserThread extends Thread{private final Gate gate;private final String myname;private final String myaddress;public UserThread (Gate gate, String myname, String myaddress){this.gate = gate;this.myname = myname;this.myaddress = myaddress;}public void run() {System.out.println (myname + " start");while (true) {gate.pass(myname, myaddress);}}
}
Main.class
public class Demo {public static void main(String[] args) {System.out.println("Testing Gate, hit CIRLtC to exit");Gate gate = new Gate();new UserThread(gate,"Axxxx","Alaska").start();new UserThread(gate,"Bxxxx","Brazil").start();new UserThread(gate,"Cxxxx","Canada").start();}}
执行结果

请添加图片描述

分析

我们看一下这个执行的结果,每个线程开始执行的时候都会先打印一下穿过这个门的人姓名,同时我们预想中的 broken 出现了,但是好像又和预想的有点不一样。我们预想的结果是出现broken时,后面的人名和出生地不对应的,但是咱们这里出现broken时,人名和出生地大部分都是对应的,从结果来看只有一条记录是不对应的。这是为什么呢?

同时我们也能看到第一次出现broken的时候已经是第128次了,侧面的说明了有些时候仅仅靠测试是无法验证程序的安全性。

从第一条broken的打印结果来看,明明已经是姓名和出生地不匹配了,但是打印的结果是匹配的,这也从侧面说明了,在多线程的程序调试过程中,调试的结果是不可信的,值得注意。

原因分析:

在这里是以语句当作线程的基本操作单位,事实上线程可能是以更小的单位在切换的。

我们的pass方法;

public void pass (String name, String address) {this.counter++;this.name = name;this.address = address;check();}

pass( ) 方法里面有四条语句,每个多线程在执行的时候,可能会是交错依次执行这四条语句的。通常线程是不会去考虑其他线程的,它只会自己一直不停的跑下去。所以在这里,可能线程A刚给name赋值完,此时线程A会继续给address赋值,但是在线程A给address赋值的同时,线程B也在给name重新赋值,然后线程A去调用check( )方法时就会出现broken,当出现broken的时候线程A会继续调用toString( )方法,此时线程B又给address赋值完了,然后toString()方法打印的就会是线程B操作的name和address,线程A在进行check( )方法检查的时候,使用的是线程B的name和线程A的address进行判断的。所以就会出现我们最终在控制台打印的结果。

修改成线程安全版本

这里我们只需要加固这个门就可以实现安全问题,我们将门的pass( )和check( )方法进行线程安全处理就可以了 。


class Gate{//表示已经通过这道门的人数private int counter = 0;//表示通过这道门的人名private String name = "Nobody";//表示通过这道门的人的出生地private String address = "Nowhere";public synchronized void pass (String name, String address) {this.counter++;this.name = name;this.address = address;check();}public String toString() {return "No." + counter + ": " + name + ", " + address;}/*** 这里为了判断人名和地址是否匹配直接看 人名 和 出生地 的首字母是否一致进行判断了*/private synchronized void check(){if (name.charAt(0) != name.charAt(0)){System.out.println("********* broken *********** " + toString());}}
}

.

在本案例中,Gate类就担任了共享资源参与者的角色。

对于pass( )和check( )这两个不安全的方法我们使用synchronized修饰符进行修饰后,就可以保证它的线程安全问题。

synchronized

synchronized修饰符并不会对程序的安全造成危害,但是调用synchronized修饰的方法时会比调用一般方法花费比较多的一些时间,所以会使程序的性能降低。

在单线程程序中使用synchronized 方法,就好像在外独居的人,即使一个人在 家中还是将厕所的门锁住的意思一样。既然是 一个人住,即使不锁门,也不需要担 心有人会突然打开厕所的门 了。

就算是多线程程序,如果所有线程完全地独立运行,那也没有使用 单线程执行模式 的必要。我们將这个状态称为线程互不干涉(interfere)。 有些管理多线程的环境,会帮我们确保线程的独立性,这种情况下这个环境的 用户就不必考虑需不需要使用单线程执行模式。

生命性与死锁

使用 单线程执行模式 时,可能会有发生死锁 (deadlock )的危险。 所谓的死锁,是指两个线程分别获取了锁定,互相等待另一个线程解除锁定的 现象。发生死锁时,哪个线程都无法继续执行下去,所以程序会失去生命性。

死锁小案例背景

假设 A 与 B 同吃一个大盘子所盛放的意大利面。盘子的旁边只有一支汤匙与一支叉子,而要吃意大利面时,同时需要用到汤匙与叉子。

只有 一支的汤匙,被 A 拿去了,两只有一支的叉子,却被 B 拿走了。 就造成以 下的情况:

  • 握着汤匙的 A ,一直等着B 把叉子放下
  • 握着叉子的 B , 一直等着A 把汤匙放下

这么一来 A 与 B 只有面面相觑,就这样不动了。像这样,多个线程僵持不下,使程序无法继续运行的状态,就称为死锁。

死锁出现条件

单线程执行达到下面这些条件时,可能会出现死锁的现象:

  • 具有多个SharedResource(共享资源) 参与者
  • 线程锁定一个SharedResource 时,还没解除前就去锁定另 一个SharedResource。
  • 获取SharedResource 参与者的顺序不固定 (和SharedResource 参与者是 对 等 的 )。

对比一下上面吃意大利面的例子:

  • 多个SharedResource 参与者,相当于汤匙与叉子。

  • 锁定某个SharedResource 参与者后,就去锁定其他SharedResource。就相当于握着汤匙而想 要获取对方的叉子,或握着叉子而想要获取对方的汤匙这些操作。

  • SharedResource角色是对等的,就像“拿汤匙一拿叉子”与“拿叉子一拿汤匙 〞两个操作都可能发生。也就是说在里汤匙与叉子并没有优先级 。

上面的三个条件只要破坏一种条件,就可以避免死锁的发生。

synchronized 在保护什么

请读者阅读程序代码的时候,看到synchronized 时,就要思考:

“这个synchronized 是在保护什么?”
无论是synchronized 方法或是synchronized块,syncbronized势必保护着“某个 东西”。

确认 “ 保护什么” 之后,接 下来应该要思考的是:

“ 其他的地方也有妥善保护到吗?”

若这些字段还在其他许多地方使用着,这里使用synchronized 小心保护,但其他地方并没有做好保护措施, 那其实这个字段还是没被保护的。就像就算小心翼翼地把大门跟后门都锁得好好的, 如果窗户敞开, 还是没有意义一样

“ 获取谁的锁定来保护的呢?,

要调用synchronized 实例方法 (instance method)的线程, 一定会获取this 的锁 定。一个实例的锁定,同一时问内只能有 一线程可以得到。因为这个惟一性,我们 才能使用synchronized 来做到单线程执行模式 如果实例不同,那锁定也不同了。虽然我们说“使用synchronized 来保护”,但 如果有多个相异实例,那多个线程仍然可以分别执行不同实例的 synchronized 方法 。

案例-2

##### 背景:

案例-1的死锁小案例简化版,汤匙和叉子,我们这里简称左手工具和右手工具,

A和B吃饭需要左手和右手配合,两只手都需要拿到对应的工具才可以吃饭。

代码:
public class Demo {public static void main(String[] args) {System.out.println("Testing Gate, hit CIRLtC to exit");Tools left = new Tools("left");Tools right = new Tools("right");new EatThread("A",left,right).start();new EatThread("B",right,left).start();}}class EatThread extends Thread{private String name ;private final Tools leftTool;private final Tools rightTool;public EatThread(String name, Tools leftTool, Tools rightTool) {this.name = name;this.leftTool = leftTool;this.rightTool = rightTool;}@Overridepublic void run() {while (true){eat();}}public void eat(){synchronized (leftTool){//左手工具拿起System.out.println(name + " " + "拿到了 " + leftTool.toString());synchronized (rightTool){//右手工具拿起System.out.println(name + " " + "拿到了 " + rightTool.toString());System.out.println(name + "开始吃东西了========> eat");System.out.println(name + "放下了右手" + leftTool.toString());}//右手工具放下System.out.println(name + "放下了左手" + rightTool.toString());}//左手工具放下}}class Tools{private final String name ;public Tools(String name) {this.name = name;}@Overridepublic String toString() {return name ;}
}
分析

上面的代码运行后会发生死锁,是因为main方法中A和B线程传参数是对称传入,这就造成了A和B拿起左右手工具时顺序不一致,就会造成A和B分别拿到一个工具,并且另一个工具都在对方手中,都在等对方放下手中的工具。

纠正
方案1

修改main方法中的A和B的传参数顺序一致,这样A和B都同时按照同一种顺序拿取工具,先拿左手工具再去拿右手工具,当左手工具没有拿到的时候就不会再去拿右手工具。

public static void main(String[] args) {System.out.println("Testing Gate, hit CIRLtC to exit");Tools left = new Tools("left");Tools right = new Tools("right");new EatThread("A",left,right).start();new EatThread("B",left,right).start();
}
方案2

将左右手工具放在一起,不分开,这样工具只有一个,谁拿到就是谁的,拿到了就可以直接开吃,不用再去拿第二个工具。

public static void main(String[] args) {System.out.println("Testing Gate, hit CIRLtC to exit");Tools left = new Tools("left");Tools right = new Tools("right");Pair pair = new Pair(left,right);new EatThread("A",pair).start();new EatThread("B",pair).start();}

http://www.ppmy.cn/news/469691.html

相关文章

Java分类递归优化

分类递归很多功能都可以遇到的但是如果数据特别大的情况下就会很慢了 原代码 List<CategoryEntity> res entities.stream()// 过滤找出一级分类.filter(categoryEntity -> categoryEntity.getParentCid() 0)// 处理&#xff0c;给一级菜单递归设置子菜单.peek(men…

二叉树oj以及前中后序非递归写法

1. 根据二叉树创建字符串 给你二叉树的根节点 root &#xff0c;请你采用前序遍历的方式&#xff0c;将二叉树转化为一个由括号和整数组成的字符串&#xff0c;返回构造出的字符串。 空节点使用一对空括号对 “()” 表示&#xff0c;转化后需要省略所有不影响字符串与原始二叉…

正则表达式中+ 与 * 有啥区别?

在正则表达式中&#xff0c;"“和”*"都是量词&#xff0c;用于指定前面的模式可以重复出现的次数。它们之间的区别如下&#xff1a; “”&#xff08;一次或多次&#xff09;&#xff1a;表示前面的模式必须出现至少一次或更多次。它要求前面的模式在匹配中至少出现…

mongo 副本集部署

当前我们使用docker-compose 的方式部署mongodb 副本集。当然&#xff0c;最佳还是使用kubernetes进行mongodb副本集的部署。 环境准备 1.安装docker&#xff0c;docker-compose 生成keyFile MongoDB使用keyFile认证&#xff0c;副本集中的每个MongoDB实例使用内容作为认证…

耳朵疼痛, 导致整个脸都疼痛并且张不开嘴 , 因为张嘴的时候耳后的肌肉疼痛---外耳道炎

近期亲身经历了这种疾病&#xff0c; 经过就诊查明 &#xff0c; 基本原因是因为外耳道损伤引起的伤口细菌感染 &#xff0c; 造成外耳道炎 &#xff0c; 用头孢 连续3天&#xff0c; 外加耳道使用氧氟沙星滴耳液 &#xff0c; 和酒精碘伏棉球消毒 每日三次 &#xff0c; 三天…

UE5下载完打开就崩溃,和用的A卡有关吗

显卡AMD XR 6600XT 内存16G,刚下载完打开就提示GPU崩溃或D3D设备已移除&#xff0c;创建了TdrDelay和TdrDdiDelay两个新注册表的方法不行&#xff0c;卸载Bridge插件也没有用。 崩溃报错&#xff1a;Fatal error: [File:D:\build\UE5\Sync\Engine\Source\Runtime\D3D12RHI\Priv…

RK系列(RK3568) USB hub SD卡热插拔支持

SOC:RK3568 kernel版本:4-19 平台:Android12 问题:GL852L是一款经常用于读卡器的芯片,目前项目上的sd卡由GL862L进行扩展,发现热插拔的时候系统没有反应不支持,查看内核配置也没有这个功能。于是一直研究解决这个问题。 后来发现在应用层可以输入命令打开关闭重新扫描USB…

耳油适合带入耳式耳机吗,试试这几款不入耳的骨传导耳机

骨传导耳机相对于普通耳机&#xff0c;是一种将声音通过人体的颅骨、骨迷路、内耳淋巴液振动膜、听神经等途径传递给听觉中枢&#xff0c;从而使人产生不一样的听觉感受。相比于传统耳机&#xff0c;骨传导耳机不用塞住耳朵&#xff0c;不会对耳膜产生损害等&#xff0c;更适合…