【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.rhkb.cn/news/431730.html

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

相关文章

机器学习04-逻辑回归(python)-02原理与损失函数

​​​​​​​ 1. 逻辑回归概念 逻辑回归&#xff08;Logistic Regression&#xff09; 是一种 分类模型&#xff0c;主要用于解决 二分类问题&#xff08;即分成两类&#xff0c;如是否通过、是否患病等&#xff09;。逻辑回归的目标是根据输入的特征预测一个 概率&#xff0…

AI大模型项目实战v0.2: 结合个人知识库

前言 在AI大模型项目实战v0.1版本中&#xff0c;我们实现了一个最简单的基于纯LLM的问答机器人Tbot。 今天升级到v0.2版本&#xff0c;结合个人知识库。 本系列每个版本&#xff0c;都将提供完整的代码文档&#xff0c;获取方法见文末。 下面开启我们的v0.2版本之旅。 v0.2 Tb…

Icarus翼星求生教你使用服务器开服

1、购买后登录服务器&#xff08;百度莱卡云游戏面板&#xff09; 登录面板的信息在绿色的登陆面板按键下方&#xff0c;不是你的莱卡云账号 进入控制面板后会出现正在安装的界面&#xff0c;大约10分钟左右就能安装完成 2、创建端口 点击目录上的网络&#xff0c;再次页面下点…

中伟视界:AI算法如何精准识别井下与传送带上堆料,提升矿山安全生产效率,减少事故风险

传送带堆料分为两种情况&#xff0c;一种是传送带的井下堆料检测AI算法&#xff0c;一种是传送带上面的堆料检测AI算法&#xff0c;传送带井下堆料检测AI算法是在带式输送机的漏煤下方井下安装摄像仪&#xff0c;通过视频分析检测井下堆煤情况&#xff0c;当洒煤堆积到一定程度…

【Git入门】使用 Git 进行项目管理:Word Count 程序开发与托管

在软件开发过程中&#xff0c;版本控制工具是不可或缺的。Git 作为一款强大的分布式版本控制工具&#xff0c;为开发者提供了高效的代码管理和协作方式。本博客将介绍如何下载安装 Git 版本管理工具&#xff0c;并使用 Git 和 GitHub 平台进行一个名为 Word Count 的项目开发与…

二分

LeetCode34 在排序数组中查找元素的第一个和最后一个位置&#xff08;二分模板题&#xff0c;左闭右开写法&#xff09; /** lc appleetcode.cn id34 langcpp** [34] 在排序数组中查找元素的第一个和最后一个位置*/// lc codestart #include<iostream> using namespace s…

Python发送邮件教程:如何实现自动化发信?

Python发送邮件有哪些方法&#xff1f;如何利用python发送邮件&#xff1f; 无论是工作汇报、客户通知还是个人提醒&#xff0c;邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件&#xff0c;实现自动化发信&#x…

逆向推理+ChatGPT,让论文更具说服力

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 使用ChatGPT辅助“逆向推理”技巧&#xff0c;可以显著提升论文的质量和说服力。逆向推理从结论出发&#xff0c;倒推所需的证据和论点&#xff0c;确保整个论证过程逻辑严密且无漏洞。…

Spring Cloud :Hystrix实现优雅的服务容错

目录 Hystrix概述&#xff1a;第一个Hystrix程序步骤1&#xff1a;创建父工程hystrix-1步骤2&#xff1a;改造服务提供者步骤3&#xff1a;改造服务消费者为Hystrix客户端&#xff08;1&#xff09;添加Hystrix依赖&#xff08;2&#xff09;添加EnableHystrix注解&#xff08;…

编程练习2 数据单元的变量替换

示例1: 1,2<A>00 示例2: 1,2<A>00,3<A>00 示例3: <B>12,1,2<B>1 示例4: <B<12,1 输出依次如下&#xff1a; #include<iostream> #include<vector> #include<string>using namespace std;/* 字符分割函数 将传入…

人工智能-大语言模型-微调技术-LoRA及背后原理简介

1. 《LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS》 LORA: 大型语言模型的低秩适应 摘要&#xff1a; 随着大规模预训练模型的发展&#xff0c;全参数微调变得越来越不可行。本文提出了一种名为LoRA&#xff08;低秩适应&#xff09;的方法&#xff0c;通过在Transf…

STM32 使用 CubeMX 实现按键外部中断

目录 问题背景知识参考需要改什么注意尽量不要在中断函数使用 循环函数做延时中断函数中延时方法调试 问题 我想实现按钮触发紧急停止类似功能&#xff0c;需要使用按键中断功能。 背景知识 GPIO 点亮 LED。stm32cubemx hal学习记录&#xff1a;GPIO输入输出。STM32—HAL库 …

活动系统开发之采用设计模式与非设计模式的区别-后台功能总结

1、数据库ER图 2、后台功能字段 题目功能字段 数据列表 编号题目名称选项数量状态 1启用0禁用创建时间修改时间保存 题目名称选项集 选项内容是否正确答案 1正确0错误启禁用删除素材图库功能字段 数据列表 编号原文件名称文件类型文件大小加密后文件名文件具体路径上传类型状态…

从零开始学习Python

目录 从零开始学习Python 引言 环境搭建 安装Python解释器 选择IDE 基础语法 注释 变量和数据类型 变量命名规则 数据类型 运算符 算术运算符 比较运算符 逻辑运算符 输入和输出 控制流 条件语句 循环语句 for循环 while循环 循环控制语句 函数和模块 定…

29 C 语言中的随机数实现:rand 与 srand

目录 1 为什么需要随机数&#xff1f; 1.1 背景介绍 1.2 应用场景 2 C 语言实现随机数 2.1 rand() 函数 2.1.1 函数原型 2.1.2 功能说明 2.1.3 案例演示 2.2 srand() 函数 2.2.1 函数原型 2.2.2 功能说明 2.2.3 案例演示 2.3 指定范围的随机数 2.3.1 获…

【JavaEE】数据链路层协议和DNS

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 &#x1f45c;一.以太网 以太网&#xff08;Ethernet&#xff09;是一种局域网技术&#xff0c;它定义了开放系统互连&#xff08;OSI&#xff09;模型中的物理…

Python网络爬虫获取Wallhaven壁纸图片(源码)

** 话不多说&#xff0c;直接附源码&#xff0c;可运行&#xff01; ** import requests from lxml import etree from fake_useragent import UserAgent import timeclass wallhaven(object):def __init__(self):# yellow# self.url "https://wallhaven.cc/search?co…

K8S介绍---搭建集群

Kubernetes介绍 官网&#xff1a;https://kubernetes.io/ 一、应用部署方式演变 1、传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点&#xff1a;简单&#xff0c;不需要其他技术的参与 缺点&#xff1a;不能为应用程序定义资源使用边界&a…

Java中List、ArrayList与顺序表

List、ArrayList与顺序表 List什么是List常用方法介绍List的使用 ArrayList与顺序表线性表顺序表接口的实现 ArrayList简介ArrayList的使用ArrayList的构造ArrayList的常见操作ArrayList的遍历ArrayList的扩容机制 ArrayList的具体使用杨辉三角简单的洗牌算法 ArrayList的问题及…

双向链表的基本结构及功能实现

1.基本结构: 双向链表是一种链表数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含三个部分&#xff1a; (1).数据域&#xff1a;存储节点的数据 (2).前驱指针:指向前一个节点 (3).后驱指针:指向下一个节点 2.基本特性&#xff1a; 双向链接: 与单向链表…