【JavaEE】——线程的安全问题和解决方式

阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:问题引入

二:问题深入

1:举例说明

2:图解双线程计算

​编辑

3:线程不安全原因的总结

(1)根本原因

(2)代码结构

(3)直接原因

(4)内存可见性问题

(5)指令重排序问题

5:解决问题的思路

(1)针对根本原因解决

(2)针对代码结构的原因解决

(3)针对直接原因——加锁

三:synchronized关键字(加锁)

1:synchronized

2:核心内容

(1)含义

(2)代码解释:

①“锁竞争”

②“加锁”

③“同一对象”

④“都要加锁”

3:变式

变式①:this

变式②: 类名.class

变式③:public前加synchronized

变式④:static方法前加synchronized(少见)


一:问题引入

用多线程,让计数器自增到1_0000

package thread;public class ThreadDemon19 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count ++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {count ++;}});t1.start();t2.start();t1.join();t2.join();System.out.println("双线程的计算结果是:"+count);}}

package thread;public class ThreadDemon20 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 1_0000; i++) {count ++;}});t1.start();t1.join();System.out.println("单线程的计算结果是:"+count);}}

 6ac3e10de1ea41f98d6f5891e5102852.png958d008681f84d10bceb10abe287dbd0.png

通过上述代码的举例,我们发现解决同一个问题,怎么最后的结果会不一样呢,真是奇了怪了。

二:问题深入

结果不一样,猜测是循环自增代码这一块出现问题

1:举例说明

我们知道cpu可以读取指令,解析指令,执行指令此处我们重点关注执行指令

count++,实际由三个指令构成的

(1)load:从内存中读取数据到cpu的寄存器当中

(2)add:把寄存器当中的值+1

(3)save:把寄存器当中的值写回到内存中

2:图解双线程计算

83b697bde121487f9842153d9f3a0844.png

c078365d354b4dce8eeaa2523e0ba043.png

t1,t2双线程的运行下,可能同一次读取操作中,t1和t2都读取到的是没有自增的数

可以通俗的理解,本来t1由数字1自增后到2,t2读取的应该是2,然后自增到3.

但是如果t2 在 t1把自增后的2 save回寄存器中  之前  读取的话 t2读到的就是1,最后只能自增到2

(可以理解成被覆盖了)

所以这就出现了矛盾(计算的数据越小矛盾越小,因为cpu运行速度很快,可能第一个线程运行结束了,第二个线程还没有开始运行)

(以上可以画出无数种情况,比如t1线程自增了2次,3次,t2线程才执行了1次。)(这就是线程的随机调度和抢占式执行)

3:线程不安全原因的总结

(1)根本原因

是线程的“抢占式执行和随机调度”

(2)代码结构

多个线程可以修改同一变量

(3)直接原因

是上述多线程修改变量这一操作并不是“原子性”的,而是可拆分的(就像我们上面画的图),这里就是操作系统底层结构的问题了

(4)内存可见性问题

(5)指令重排序问题

(4)(5)条上述代码没有涉及,我们后续再详细引入

5:解决问题的思路

为了确保结果的正确,我们得确保第一个线程save了过后,第二个线程再去load。这时第二个线程load到的数据才是自增过后正确的数据

(1)针对根本原因解决

不可行。如果要修改线程的“抢占式执行和随机调度”这一机制的话,就得修改操作系统中的内核,相当于是重新写了一个“新的系统”

(2)针对代码结构的原因解决

有些地方,代码结构可以进行修改,但是有些地方不可以,视情况而论

(3)针对直接原因——加锁

count++,由三个指令完成,我们如果给这三个指令打包成一个整体的话就可以避免线程出现问题了,也就是“加锁”

三:synchronized关键字(加锁)

1:synchronized

关键字:synchronized(对象){加锁内容};

注:synchronized的加锁依赖于对象

2:核心内容

(1)含义

如果第一个线程对某个对象上锁之后,第二个线程要想对同一个对象上锁,就必须等第一个线程释放锁,此时第二个线程是处于BLOCKED阻塞状态

package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-21* Time: 15:27*/
public class ThreadDemon21 {private static long count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();//创建一个object对象Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {synchronized (locker){  //锁到object这个对象上count++;}}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {synchronized(locker){   //锁到object这个对象上count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("计算结果是:" + count);}}

34ecb5cf59ec417d9278c9f3ee3dd8c0.png

(2)代码解释:

8265d6d44fe34545b81cad721dd02fb4.png

①“锁竞争”

通过“锁竞争”让让第二个线程无法插入到第一个线程的执行指令当中。

②“加锁”

“加锁”就是把count++中三个指令(load,add,save)打包成一个“原子性”的操作(最小单位的操作,再也不可分割了)

③“同一对象”

加锁的对象不同,“锁竞争”就不会发生,线程安全问题依旧存在

④“都要加锁”

如果第一个线程加锁,第二个线程不加锁,“锁竞争”也不会发生,线程安全问题依旧存在

3:变式

变式①:给this加锁

this指向的还是同一个对象,t1和t2依旧会产生“锁竞争”

package thread;/*** Created with IntelliJ IDEA.* Description:* User: Hua YY* Date: 2024-09-21* Time: 15:27*/
class Test{public long count = 0;public void add(){synchronized (this){count++;}}}
public class ThreadDemon22 {public static void main(String[] args) throws InterruptedException {Test test = new Test();Thread t1 = new Thread(()->{for (int i = 0; i < 5000; i++) {test.add();}});Thread t2 = new Thread(()->{for (int i = 0; i < 5000; i++) {test.add();}});t1.start();t2.start();t1.join();t2.join();System.out.println("计算结果是:" + test.count);}
}
变式②: 类名.class

获取到当前类的对象,类对象包含了这个类的各种信息(类名字,属性,方法.......)

0d9678cdd4cc43c980ea87a69badfdf5.png

变式③:public前加synchronized

等价写法

149e4cdcd5764beea70abfd90e40972c.png26e85566700e4e468089ca8f887ae40e.png

变式④:给类对象加锁(static)
static方法前加synchronized(少见)

7907e54c80374913a85e7238d89f2940.png16e0df5e0bd34a6791810c9b39b7ebe5.png

如果synchronized加到static方法上,就相当于给类对象加锁了

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

springboot数字化超市管理系统—计算机毕业设计源码34804

摘 要 在网络信息的时代&#xff0c;众多的软件被开发出来&#xff0c;给用户带来了很大的选择余地&#xff0c;而且人们越来越追求更个性的需求。在这种时代背景下&#xff0c;超市只能以用户为导向&#xff0c;按品种小批量组织生产&#xff0c;以产品的持续创新作为超市最重…

ONES 与华为云深度合作,共同打造企业智能研发管理平台

9月20日&#xff0c;在华为全联接大会&#xff08;HUAWEI CONNECT 2024&#xff09;上&#xff0c;深圳复临科技有限公司&#xff08;以下简称“ONES”&#xff09;与华为云计算技术有限公司&#xff08;以下简称“华为云”&#xff09;正式签署合作协议&#xff0c;双方将在企…

Python | Leetcode Python题解之第421题数组中两个数的最大异或值

题目&#xff1a; 题解&#xff1a; class Trie:def __init__(self):# 左子树指向表示 0 的子节点self.left None# 右子树指向表示 1 的子节点self.right Noneclass Solution:def findMaximumXOR(self, nums: List[int]) -> int:# 字典树的根节点root Trie()# 最高位的二…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.6 处理器架构2 函数

新的架构来了&#xff0c;本游戏的最后一个攻略分享&#xff0c;最后汇编部分无非是对于操作码的熟练&#xff0c;硬件没有问题&#xff0c;那么也就无关痛痒了。 汇编实现&#xff0c;两数相或和两数相与非一起相与即可。 八位异或器&#xff0c;整就完事了。 有手就行。 利…

十七、RC振荡电路

振荡电路 1、振荡电路的组成、作用、起振的相位条件以及振荡电路起振和平衡幅度条件&#xff0c; 2、RC电路阻抗与频率、相位与频率的关系曲线; 3、RC振荡电路的相位条件分析和振荡频率

秩一的等价转化

Lemma 2. For a positive semi-definite Hermitian matrix A ∈ C M M \mathbf{A}\in\mathbb{C}^{M\times M} A∈CMM, the condition Rank ( A ) 1 \left(\mathbf{A}\right)1 (A)1 is equivalent to t h e following conditions the\textit{ following conditions} the fol…

JavaWeb 实验一

实验一 环境配置和Web工程的创建 目的&#xff1a; 掌握Java Web编程环境的配置创建简单的Web工程&#xff0c;并了解Web工程下各目录的作用掌握部署、运行Web工程的流程 实验过程&#xff1a; 一、完成如下要求。 安装并设置JDK 1.8、Tomcat 9.0&#xff08;tomcat和jdk版…

PDF转JPG神器!一键转换,轻松搞定文档分享难题

亲爱的朋友们&#xff0c;你是不是也常常被PDF文件格式所困扰&#xff1f;特别是当你想要将PDF文件转换为JPG格式时&#xff0c;是否觉得有些头大呢&#xff1f;别担心&#xff0c;今天我将向你推荐五款非常棒的PDF转JPG工具&#xff0c;它们绝对能让你的转换过程轻松愉快&…

暴力枚举算法

《啊哈&#xff01;算法》学习笔记 本博客的题目仅用暴力枚举&#xff0c;并不一定是最好的解法&#xff0c;主要是了解枚举算法 例题一&#xff1a;两方框奥数 在两个方框内填入相同的数字使得等式成立&#xff1a; 代码如下&#xff1a; for(i1;i<9;i) {if((i*103)*652…

数据结构---二叉搜索树(二叉排序树)

什么是二叉排序树 二叉搜索树又是二叉排序树&#xff0c;当我们的是一颗空树或者具有以下性质时&#xff1a; 左子树不为空&#xff0c;左子树上的值都小于我们的根节点上的值。右子树不为空时&#xff0c;右子树上的值都大于我们的根节点上的值左右子树都是二叉搜索树&#…

Java异常架构与异常关键字

1. Java异常简介 Java 异常是 Java 提供的一种识别及响应错误的一致性机制。 Java 异常机制可以使程序中异常处理代码和正常业务代码分离&#xff0c;保证程序代码更加优雅&#xff0c;并提高程 序健壮性。在有效使用异常的情况下&#xff0c;异常能清晰的回答 what, where,…

2023北华大学程序设计新生赛部分题解

时光如流水般逝去&#xff0c;我已在校园中奋战大二&#xff01;(≧▽≦) 今天&#xff0c;静静回顾去年的新生赛&#xff0c;心中涌起无尽感慨&#xff0c;仿佛那段青春岁月如烟花般绚烂。✧&#xff61;(≧▽≦)&#xff61;✧ 青春就像一场燃烧的盛宴&#xff0c;激情澎湃&…

《高等代数》线性相关和线性无关(应用)

说明&#xff1a;此文章用于本人复习巩固&#xff0c;如果也能帮到大家那就更加有意义了。 注&#xff1a;1&#xff09;线性相关和线性无关的证明方法中较为常用的方法是利用秩和定义来证明。 2&#xff09;此外&#xff0c;线性相关和线性无关的证明常常也会用到反证法。 3&…

2.5 数据库索引机制

我们往数据表里面保存数据记录越来越多&#xff0c;一旦达到上千万条&#xff0c;那怎么提高检索速度就需要认真考虑了。我们打开手机上的APP都希望能快些加载出内容&#xff0c;这里的因素有很多&#xff0c;但是如何减少数据查找的时间是其中的重要一环。索引机制就是提升数据…

怎么给视频加字幕?6种给视频加字幕最简单的方法,不怕你学不会!

在这个快节奏的时代&#xff0c;越来越多的人会在公共场所观看视频&#xff0c;但为了不影响其它人&#xff0c;大多数人往往会将声音关闭。统计数据显示&#xff0c;高达69%的人在这样的环境中会静音观看视频&#xff0c;而有字幕的情况下&#xff0c;80%的人更倾向于完整观看…

刷题训练之栈

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟练掌握字符串算法。 > 毒鸡汤&#xff1a;学习&#xff0c;学习&#xff0c;再学习 ! 学&#xff0c;然后知不足。 > 专栏选自&#xff1a;刷题…

C++之spring

C之spring string类对象的访问及遍历操作 operator[] 返回pos位置的字符&#xff0c;const string类对象调用 这是一个既可以写也可以读的库函数&#xff0c;const修饰的内容是不可以更改的&#xff0c;所以是读 C类与对象里要想普通对象和const修饰的对象同时重载 第二种访…

2024华为杯研究生数学建模,代码思路和参考文章

F题X射线脉冲星光子到达时间建模&#xff0c; E题高速公路应急车道紧急启用模型&#xff0c; D题大数据驱动的地理综合问題&#xff0c; C题数据驱动下磁性元件的磁芯损耗建模&#xff0c; B题W LAN 组网中网络吞吐量建模&#xff0c; A题风电场有功功率优化分配&#xff…

Python画笔案例-057 绘制蜘蛛网

1、绘制蜘蛛网 通过 python 的turtle 库绘制 蜘蛛网&#xff0c;如下图&#xff1a; 2、实现代码 绘制蜘蛛网&#xff0c;以下为实现代码&#xff1a; """蜘蛛网.py """ import turtledef draw_circle(pos,r):"""pos:圆的中心点…

力扣最热一百题——搜索二维矩阵

目录 题目链接&#xff1a;240. 搜索二维矩阵 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 解法一&#xff1a;暴力不解释 Java写法&#xff1a; 运行时间 C写法&#xff1a; 运行时间 时间复杂度以及空间复杂度 解法二&#xff1a;利用自带的大小关系进行Z型走…