当前位置: 首页 > news >正文

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 类的方法,也可以看到类似的操作


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

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

相关文章:

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

这里是Themberfue 在上一节的最后&#xff0c;我们讨论两个线程同时对一个变量累加所产生的现象 在这一节中&#xff0c;我们将更加详细地解释这个现象背后发生的原因以及该如何解决这样类似的现象 线程安全问题 public class Demo15 {private static int count 0;public …...

(已解决)Dependency “ ” not found 细谈

剖析原因&#xff1a;依赖在pom文件中引用后&#xff0c;然后ReLoad&#xff0c;此依赖会在你配置的本地仓库里面找&#xff0c;并下载下来&#xff0c;他报not found就是没有找到。 本地仓库的位置&#xff1a; 进一步深究&#xff1a;在本地仓库找的时候&#xff0c;他又会…...

网络编程、UDP、TCP、三次握手、四次挥手

一、初识网络编程 网络编程的概念&#xff1a;在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件等等。 不管是什么场景&#xff0c;都是计算机和计算机之间通过网络进行…...

程序员的生活周刊 #7:耐克总裁被裁记

0. 庙宇 这张图来自 Tianshu Liu&#xff0c; 被树木环绕的宝塔庙宇 1. 耐克总裁 耐克最近的总裁 John Donahoe 干了 5 年&#xff0c;终于被裁掉了。 这位总裁即不了解球鞋文化&#xff0c;也没有零售经验&#xff0c;但不懂事的董事会还是聘用它&#xff0c;寄托把耐克从运…...

sparkSQL的UDF,最常用的regeister方式自定义函数和udf注册方式定义UDF函数 (详细讲解)

- UDF&#xff1a;一对一的函数【User Defined Functions】 - substr、split、concat、instr、length、from_unixtime - UDAF&#xff1a;多对一的函数【User Defined Aggregation Functions】 聚合函数 - count、sum、max、min、avg、collect_set/list - UDTF&#xff1a;…...

【Ubuntu20】VSCode Python代码规范工具配置 Pylint + Black + MyPy + isort

​ 常用工具&#xff1a; 在 Ubuntu20 下&#xff0c;有以下常见的 Python 代码工具&#xff1a; 静态分析工具&#xff1a; Pylint 和 Flake8 功能范围&#xff1a;Pylint功能非常强大&#xff0c;能够检查代码质量、潜在错误、代码风格、复杂度等多个方面, 并生成详细的报…...

游戏提示错误:xinput1_3.dll缺失?四种修复错误的xinput1_3.dll文件

在计算机的运行过程中&#xff0c;我们可能会遇到各种各样的问题&#xff0c;其中与“xinput1_3.dll”相关的问题也并不罕见。“xinput1_3.dll”是一个在许多游戏和多媒体应用程序运行过程中可能会用到的动态链接库文件。当我们启动某些游戏时&#xff0c;可能会突然弹出一个错…...

YOLOv11融合IncepitonNeXt[CVPR2024]及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/abs/2303.16900 代码链接&#xff1a;https://github.com/sail-sg/inceptionnext 论文速览&#xff1a;受 ViT 长距离建模能力的启发&#xff0c;大核卷积…...

[Web安全 网络安全]-学习文章汇总导航(持续更新中)

文章目录&#xff1a; 一&#xff1a;学习路线资源 1.路线 2.资源 二&#xff1a;工具 三&#xff1a;学习笔记 1.基础阶段 2.进阶阶段 四&#xff1a;好的参考 五&#xff1a;靶场 博主对网络安全很感兴趣&#xff0c;但是不知道如何取学习&#xff0c;自己一步一步…...

Docker Compose部署Rabbitmq(Docker file安装延迟队列)

整个工具的代码都在Gitee或者Github地址内 gitee&#xff1a;solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb github&#xff1a;GitHub - ZeroNing/solomon-parent: 这个项目主要是…...

SpringBoot+FileBeat+ELK8.x版本收集日志

一、准备环境 1、ElasticSearch&#xff1a;8.1.0 2、FileBeat&#xff1a;8.1.0 3、Kibana&#xff1a;8.1.0 4、logstach&#xff1a;8.1.0 本次统一版本&#xff1a;8.1.0,4个组件&#xff0c;划分目录&#xff0c;保持版本一致。 说明&#xff1a;elasticsearch和kib…...

本地模型导入ollama

文章目录 Modelfile模板导入到 ollama Modelfile模板 在本地模型目录下创建 Modelfile FROM ./qwen2.5-7b-instruct-q4_k_m.gguf# 设定温度参数为1 [更高的更具有创新性&#xff0c;更低的更富有连贯性] PARAMETER temperature 1 # 将上下文窗口大小设置为4096&#xff0c;这…...

scala Map训练

Map实训内容&#xff1a; 1.创建一个可变Map,用于存储图书馆中的书籍信息(键为书籍编号,值为包含书籍名称、作者、库存数量的元组)&#xff0c;初始化为包含几本你喜欢的书籍信息。 2.使用 操作符添加两本新的书籍到图书馆集合中。 3.根据书籍编号查询某一本特定的书籍信息&…...

WorkFlow源码剖析——Communicator之TCPServer(下)

WorkFlow源码剖析——Communicator之TCPServer&#xff08;下&#xff09; 前言 系列链接如下&#xff1a; WorkFlow源码剖析——GO-Task 源码分析 WorkFlow源码剖析——Communicator之TCPServer&#xff08;上&#xff09; WorkFlow源码剖析——Communicator之TCPServer&…...

数据结构与算法分析:专题内容——动态规划2之例题讲解(代码详解+万字长文+算法导论+力扣题)

一、最长公共子序列 在生物应用中&#xff0c;经常需要比较两个(或多个)不同生物体的 DNA。一个 DNA 串由一串称为碱基(base)的分子组成&#xff0c;碱基有腺嘌呤、鸟嘌呤、胞嘧啶和胸腺嘧啶 4 种类型。我们用英文单词首字母表示 4 种碱基&#xff0c;这样就可以将一个 DNA 串…...

【Qt】QTreeView 和 QStandardItemModel的关系

QTreeView 和 QAbstractItemModel&#xff08;通常是其子类&#xff0c;如 QStandardItemModel 或自定义模型&#xff09;是 Qt 框架中的两个关键组件&#xff0c;它们之间存在密切的关系。 关系概述 QTreeView&#xff1a; QTreeView 是一个用于显示和编辑层次数据的视图小部…...

containerd配置私有仓库registry

机器ip端口regtisry192.168.0.725000k8s-*-------k8s集群 1、镜像上传 rootadmin:~# docker push 192.168.0.72:5000/nginx:1.26.1-alpine The push refers to repository [192.168.0.72:5000/nginx] 6961f0b8531c: Pushed 3112cd521249: Pushed d3f50ce9b5b5: Pushed 9efaf2eb…...

深入解析语音识别中的关键技术:GMM、HMM、DNN和语言模型

目录 一、高斯混合模型&#xff08;GMM&#xff09;与期望最大化&#xff08;EM&#xff09;算法二、隐马尔可夫模型&#xff08;HMM&#xff09;三、深度神经网络&#xff08;DNN&#xff09;四、语言模型&#xff08;LM&#xff09;五、ASR系统的整体工作流程结论 在现代语音…...

C++循环引用

C循环引用‌指的是两个或多个类之间互相引用对方&#xff0c;形成一个循环的引用关系。 循环引用的问题&#xff1a; 编译错误‌&#xff1a;编译器在编译过程中会按照包含关系依次编译每个文件&#xff0c;当编译ClassA时&#xff0c;它会尝试包含ClassB.h文件&#xff0c;而…...

dayseven-因果分析-图模型与结构因果模型

在数学上&#xff0c;​“图”(graph)是顶点&#xff08;vertex&#xff0c;也可以称为节点&#xff09;和边(edge)的集合&#xff0c;表示为图G(V,E)&#xff0c;其中V是节点的集合&#xff0c;E是边的集合&#xff0c;图中的节点之间通过边相连&#xff08;也可以不相连&…...

Chapter03-Authentication vulnerabilities

文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表&#xff0c;若其中包含环&#xff0c;则输出环的入口节点。 若其中不包含环&#xff0c;则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)

文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计

随着大语言模型&#xff08;LLM&#xff09;参数规模的增长&#xff0c;推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长&#xff0c;而KV缓存的内存消耗可能高达数十GB&#xff08;例如Llama2-7B处理100K token时需50GB内存&a…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...