Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized

这里是Themberfue 

· 在上一节的最后,我们讨论两个线程同时对一个变量累加所产生的现象

· 在这一节中,我们将更加详细地解释这个现象背后发生的原因以及该如何解决这样类似的现象


线程安全问题

public class Demo15 {private static int count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count++;}});// "线程安全问题"// 会照成 "线程不安全问题"// t1.start();// t2.start();// t1.join();// t2.join();// 改为串行执行t1.start();t1.join();t2.start();t2.join();System.out.println(count);}
}

· 我们先回顾上述代码,如果两个线程并发执行逻辑,同时累加 count 变量 100,000 次后,得到的结果是一个随机值,且这个随机值一定小于 100,000

· 如果改为串行执行,就是 t1 执行完后,t2 再度执行,那么 count 的结果就为 100,000

· 为什么会产生这样的现象?

· 我们先从一行代码入手:count++,有的人就会问:这有什么好分析的,这不就是一个count + 1的操作吗?没错,的确是这样

· 众所周知:CPU(中央处理器)执行的是一系列指令,这些指令定义了它需要执行的逻辑操作,这些指令的集合统称为指令集,指令集有两种,一种是..... (再讲就串台了,这是计算机组成原理的知识哦)

· 常见的指令就有逻辑指令,算术指令等,那么,一个 count++ 其实分为三个指令操作,因为它还设计到变量的修改,而不是单纯地加法

· 我们都知道,把大象放进冰箱分三步:把把冰箱门打开,把大象装进去,再把冰箱门关上

· count++ 也分为三步操作:

        1. load:把内存中 count 的值,加载到 cpu 寄存器

        2. add:把寄存器中的 count 的值 + 1

        3. save:把寄存器中的 count 的值保存到内存中

PS:寄存器就是CPU处理日常任务的小工具,用来存放临时信息

· 操作系统对线程的调度的是随机的所以在执行这三条指令时,可能不是一口气全部执行完毕,而是执行了一半就不执行了,而后又执行了

· 比如执行 指令1 ,后被调度走,调度回来后执行 指令2 指令3

· 比如执行 指令1 指令2 ,后被调度走,调度回来后执行 指令3

· 比如执行 指令1 ,后被调度走,调度回来后执行 指令 2 ,后被调度走,调度回来后执行指令3

· 多线程的随机调度是造成这个bug出现的原因

· 上述为简单模拟了一遍两次 count++ 的大概流程

· 这是最为理想的情况,就是三条指令一次性执行完毕后再去执行下三条指令,但实际情况却不能保证每次发生这种理想的情况

 

· 上述情况才是经常发生的,也是导致bug的主要原因

· 尽管执行了两次 count++ 操作,但内存中保存的值为1,结果只增值了一次

· 产生上述问题的原因就是线程安全问题

· 根本原因就是操作系统对于线程的调度是随机的,也就是抢占式执行(这个策略在最初诞生多线程任务操作系统时就诞生了,是非常伟大的发明,后世的操作系统,都是这个策略)

· 第二个原因就是多个线程修改同一个变量

        如果是一个线程修改一个变量,不会产生上述问题

        如果是多个线程修改不同变量,同样的

        如果是多个线程不是同时修改同一个变量,同样的

        如果是多个线程同时读取一个变量,同样的

· 第三个原因就是修改的操作,不是原子的,如果是  count++ 是一条指令就可以执行完毕,那么认为该操作就是原子的

· 内存可见性问题

· 指令重排序

· 后续再讨论其细节


加锁

 · Java中解决线程安全问题的最主要的方案就是给代码块加锁,通过加锁,可以让不是原子的操作,打包成一个原子的操作

· 计算机中的锁操作,和生活中的加锁区别不大,都是互斥,排他。例如:你上厕所,对当前这个厕所间加锁,那么别人就不能进这个厕所间了,你出厕所门时,此时就是解锁

· 通过使用锁,对先前的 count++ 操作就可以将其变为原子的,在加上锁后,count++ 的三个指令就会完整执行完毕后才可能被调度走

· 加锁操作,不是讲线程锁死到CPU上,禁止这个线程被调度走,是禁止其他线程重新加这个锁,避免其他线程的操作,在这个线程执行过程中插队

· 加锁和解锁这个操作本身是操作系统提供的 api,但是很多语言都对其单独进行了封装,大多数的封装风格都是采取这两个函数:

Object.lock();// 执行的代码逻辑Object.unlock();

· 但是这样写的弊端也很大,不能保证每次加上锁后都会记住去解锁,所以 Java 提供了一种更为简洁的方式去给某个代码块上锁:

synchronized {// 执行的代码逻辑}

· 只要进入了代码块(进入 '{' 后)就会加上锁,只要出了代码块(出去 '}' 后)就会自动解锁

· 但在上述伪代码中,synchronized 的使用并不正确,单纯地加锁,但是此时另一个线程又要加锁,我们要怎么判断这个锁有没有被使用(锁又不止一个)

· 所以应该这样使用:

synchronized (Object) {// 执行的代码逻辑}

· 没错,括号里填写的就是用来加锁的对象,这个对象一般称为锁对象,作为锁的作用去使用

· Object 表示一个类,Java 的所有对象都可以作为锁对象

Object locker = new Object();synchronized (locker) {count++;
}
public class Demo16 {private static int count = 0;public static void main(String[] args) throws InterruptedException {// Java中,任何一个对象都可以作为锁Object locker = new Object();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {// 对count的++操作进行上锁// load,add,save操作执行完才会调度走synchronized (locker) {count++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});// 只有两个线程针对一个对象加锁,才会产生互斥效果// 一个线程被上了锁,另一个线程得阻塞等待,直到第一个线程解锁t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + count);}
}

· 单独对一个线程上一个锁,是不能发挥锁的作用的

· 只有两个线程针对一个对象加锁,才会产生互斥效果

· 一个线程被上了锁,另一个线程就得阻塞等待,直到那个线程解锁,才会继续向下执行

· 运行上述代码,count 的结果恒为 100,000,不可能出现其他值,也就解决了该代码逻辑的线程安全问题

· 通过加锁操作,count++ 操作的三个指令相当于合并成了一个指令,保证每个线程从内存中获取到的值是正确的

· 并不是加上了 synchronized 就一定保证线程安全,得要正确地使用锁,在该使用的时候使用锁,在对的地方使用锁

· 比如,在这个案例中,不是对 count++ 操作操作,而是在 for 循环开始前就上锁:

synchronized (locker) {for (int i = 0; i < 50000; i++) {count++;}
}

· 这样虽然也是上了锁,但是没什么意义,就相当于等到 for 循环逻辑全部结束后,再解锁,另一个线程停止等待,拿到锁

· 因为这两个线程就这一个相同逻辑,所以这么写就相当于变成了串行执行,不是并发了

· 采取 synchronized 的加锁方式,就可以确保一定会释放锁,不会遇到加锁后但是没有解锁的情况

· 除此之外,synchronized 还可以修饰方法,对这个方法加锁

class Counter {private int count;// 使用 synchronized 对方法进行上锁,就相当于是针对this上锁synchronized public void addCount() {// synchronized (this) {this.count++;// }}// 使用 synchronized 对静态方法进行上锁,就相当于是针对类对象上锁(反射)public synchronized static void func () throws ClassNotFoundException {synchronized (Class.forName("Counter")) {System.out.println("func");}}public synchronized static void fuc () {synchronized (Counter.class) {System.out.println("fuc");}}public int getCount() {return count;}
}public class Demo17 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.addCount();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {counter.addCount();}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = " + counter.getCount());}
}

· 我们如果查看 StringBuffer 类的方法,也可以看到类似的操作


· 下一节我们会更加深入多线程,了解到死锁等相关概念

· 毕竟不知后事如何,且听下回分解~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59704.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React前端框架:现代网页开发的基石(附带构建简单任务管理应用案例代码)

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; React 是由 Facebook 开发并开源的前端框架&#xff0c;用于构建用户界面。它通过虚拟DOM、高效的渲染机制和组件化的开发模式&am…

在Zetero中调用腾讯云API的输入密钥的问题

也是使用了Translate插件了&#xff0c;但是需要调用腾讯云翻译&#xff0c;一直没成功。 第一步就是&#xff0c;按照这上面方法做&#xff1a;百度、阿里、腾讯、有道各平台翻译API申请教程 之后就是&#xff1a;Zotero PDF translat翻译&#xff1a;申请腾讯翻译接口 主要是…

kelp protocol

道阻且长,行而不辍,未来可期 有很长一段时间我都在互联网到处拾金,but,东拼西凑的,总感觉不踏实,最近在老老实实的看官方文档 & 阅读白皮书 &看合约,挑拣一些重要的部分配上官方的证据,和过路公主or王子分享一下,愿我们早日追赶上公司里那些可望不可及大佬们。…

如何以开源加速AI企业落地,红帽带来新解法

CSDN 看到&#xff0c;生成式 AI 的火爆正在引发计算、开发、交互三大范式全面的升级和转换&#xff0c;全行业或将迎来一次全新的科技变革周期&#xff0c;可能比移动与云计算变革更加剧烈。不过 AI 经历了追求千亿模型效果和芯片、集群硬件的军备竞赛后&#xff0c;如何真正落…

vue的组件使用

1.安装element plus组件库 npm install element-plus --save 2. 3.在页面使用

人保财险(外包)面试分享

前言&#xff1a; 这是本月面的第三家公司&#xff0c;太难了兄弟们&#xff0c;外包都不好找了&#xff0c;临近年底&#xff0c;金九银十已经错过了&#xff0c;金三银四虽然存在&#xff0c;但按照这几年的行情&#xff0c;金九银十和金三银四其实已经是不复存在了&#xf…

Linux-c TCP服务模型

1、TCP模型&#xff0c;服务端与客户端的搭建时序图 2、TCP模型&#xff0c;在创建阶段和通信阶段&#xff0c;对套接字的理解 2.1、tcp连接阶段 2.2、tcp通信状态 一个服务端与多个客户端的通信状态 TCP与UDP的对比 &#xff08;下图是笔者理解所画&#xff0c;可能也许有错…

卡码网KamaCoder 127. 骑士的攻击

题目来源&#xff1a;127. 骑士的攻击 C题解&#xff08;来源A * 算法精讲 &#xff08;A star算法&#xff09; | 代码随想录&#xff09;&#xff1a;Astar Astar 是一种 广搜的改良版。 有的是 Astar是 dijkstra 的改良版。 其实只是场景不同而已 我们在搜索最短路的时候&…

浅谈语言模型推理框架 vLLM 0.6.0性能优化

在此前的大模型技术实践中&#xff0c;我们介绍了加速并行框架Accelerate、DeepSpeed及Megatron-LM。得益于这些框架的助力&#xff0c;大模型的分布式训练得以化繁为简。 然而&#xff0c;企业又该如何将训练完成的模型实际应用部署&#xff0c;持续优化服务吞吐性能&#xf…

闯关leetcode——3222. Find the Winning Player in Coin Game

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/find-the-winning-player-in-coin-game/description/ 内容 You are given two positive integers x and y, denoting the number of coins with values 75 and 10 respectively. Alice and Bob a…

斗破QT编程入门系列之二:GUI应用程序设计基础:UI文件(四星斗师)

斗破Qt目录&#xff1a; 斗破Qt编程入门系列之前言&#xff1a;认识Qt&#xff1a;Qt的获取与安装&#xff08;四星斗师&#xff09; 斗破QT编程入门系列之一&#xff1a;认识Qt&#xff1a;初步使用&#xff08;四星斗师&#xff09; 斗破QT编程入门系列之二&#xff1a;认识…

高级 HarmonyOS主题课—— 帮助快速构建各种文本识别应用的课后习题

天地不仁&#xff0c;以万物为刍狗&#xff1b; 圣人不仁&#xff0c;以百姓为刍狗。 天地之间&#xff0c;其犹橐龠乎&#xff1f; 虚而不屈&#xff0c;动而俞出。 多闻数穷&#xff0c;不若守于中。 本文内容主要来自 <HarmonyOS主题课>帮助快速构建各种文本识别应用 …

达梦数据库DM管理工具增删改不生效怎么办?如何设置事务自动提交?

前言 我在使用达梦数据库DM时&#xff0c;一开始使用的是达梦数据库自带的连接工具DM管理工具。自带的有它自己的好处&#xff0c;起码对于修改新增字段等是比较兼容的。后面我发现DBeaver也是支持连接达梦数据库的&#xff0c;所以后面用DBeaver也在连接达梦数据库。 我在一…

力扣排序455题(分发饼干)

假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。 但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i],这是能 让孩子们满足胃口的饼干的最小尺寸;并且每块饼 干j&#xff0c;都有一个尺寸 s[j]。如果 s[j]> g[i]&…

Hadoop---MapReduce(3)

MapTask工作机制 &#xff08;1&#xff09;Read阶段&#xff1a;MapTask通过InputFormat获得的RecordReader&#xff0c;从输入InputSplit中解析出一个个key/value。 &#xff08;2&#xff09;Map阶段&#xff1a;该节点主要是将解析出的key/value交给用户编写map()函数处理&…

ssm052游戏攻略网站的设计与实现+vue(论文+源码)-kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;游戏攻略网站设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本游戏攻略网站就是在这…

Word大珩助手:超大数字怎么读?35位数字?69位数字?

俄罗斯日前对谷歌开出了20000000000000000000000000000000000&#xff08;35位数字&#xff09;美元的罚款 这一数字远超全球GDP总和&#xff0c;消息一出很快就登上热搜。 面对这样一个庞大的数字&#xff0c;人们不禁好奇&#xff0c;这样的数字该如何读出来&#xff1f; …

VisionPro —— CogIPOneImgeTool工具详解

CogIPOneImageTool工具主要用来对单张图像进行算法处理操作 CogIPOneImgeTool简介 CogIPOneImageTool 工具可完成高斯平滑、高通滤波和图像量化等基本图像处理操作。Image Processing One Image 工具编辑控件为此工具提供图形用户界面。 Image Processing Operations (图像处…

sql数据库-DQL-条件查询

条件查询 SELECT 字段列表 FROM 表名 WHERE 条件列表; 条件列表 比较运算符功能> 大于>大于等于 < 小于<小于等于等于!不等于between...and...某个范围之间&#xff08;闭区间&#xff09;IN(...)在in之后的列表中的值&#xff0c;多选一LIKE 通…

更快更强 | HP15加热台新品!Max温度350度,200度只需60秒!30~150W功率可调,恒温加热和回流焊双模式!

正点原子HP15加热台更快更强&#xff01;最高温度可达350度&#xff0c;200度只需60秒&#xff01;30~150W功率可调&#xff0c;恒温加热和回流焊双模式&#xff01; HP15是正点原子全新推出的迷你恒温加热台&#xff0c;设备支持30~150W功率可调&#xff0c;在150W功率下从室温…