Java线程 - 详解(2)
一,线程安全问题
有些代码在单个线程的环境下运行,完全正确,但是同样的代码,让多个线程去执行,此时就可能出现BUG,这就是所谓的 "线程安全问题"。举一个例子:
public class Demo {static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});Thread thread1 = new Thread(()->{for (int i = 0; i < 5000; i++) {count++;}});thread.start();thread1.start();thread.join();thread1.join();System.out.println("count = "+count);}
}
以上代码如果使用单线程执行,他的答案是10000,但是如果使用多线程,那么就不确定了。

那么为什么会出现这种情况呢?首先我们要深入了解一下 count++ 这个操作,实际上这个操作是分成 3 步执行的(站在CPU的角度,count++,是有三个指令实现的):
- load 把数据从内存读取到 CPU 寄存器中 ——> tmp = count (简单理解版)
- add 把寄存器中的数据进行 +1 ——> tmp += 1 (简单理解版)
- save 把寄存器中的数据,保存到内存中 ——> count = tmp (简单理解版)
在此基础上,又因为线程之间的调度顺序是随机的,就会导致上面的代码出BUG,画一个图来理解一下:

(上面画的只是一部分情况),也就是说,两个线程分别自增一次,预期得到的是2,实际上可能得到的是1,这就会导致两个线程的结果没有向上累加,而是各自独立运行。
讲了上面的BUG后,还有一个问题,我们得到的count的可能取值范围是多少?是[1,10000],还是[5000,10000]?答案是 [1,10000],因为可能 线程1 自增1次, 而 线程2 自增 n 次,画个图理解一下:

二,线程安全问题产生原因
1. 操作系统中,线程的调度顺序是随机的(抢占式执行)
线程的调度顺序是在系统内核实现的,无法解决
2. 两个线程,针对同一个变量进行修改
一个线程针对一个变量进行修改 ✔️
两个线程针对不同变量进行修改 ✔️
两个线程针对同一个变量进行读取 ✔️
3. 修改操作不是原子的,拿上面的举例就是:count++这个操作不是一步到位的,需要分成4三步执行。
原子性:将多个操作"封装"起来,使其就相当于一个操作,更加通俗一点,就相当于
有一个房间,当线程1在该房间执行操作时,线程2要么等待,要么去其他房间执行
4. 内存可见性问题(这章还不涉及)
5. 指令重排序问题(这章还不涉及)
三,解决线程安全问题
上面讲了5种原因导致线程安全问题,其中1是避免不了的,2是只能在写代码时尽量避免,4,5还没涉及到,因此我们要想解决线程安全问题就只能从原因3入手,即将那些非原子操作转换成原子操作,更加专业一点就是 "加锁"。
在Java中,给代码"加锁"最常见的办法就是使用 synchronized 关键字:
synchronized( ){
......
//要执行的操作放在这里
}
注:( )中需要一个用来加锁的对象。这个对象的类型不重要,重要的是通过这个对象来区分两个线程是否竞争同一个锁,如果两个线程是在竞争同一个锁,就会有锁竞争,如果不是,就不会有锁竞争,就任然是并发执行。
你可以将锁想象成一个单人的自习室,如果你先占用了这个自习室,那么其他人要么阻塞等待你使用结束(锁竞争),要么去其他空的自习室(没有锁竞争)。
public class Demo {static int count = 0;public static void main(String[] args) throws InterruptedException {Object locker = new Object();//锁Thread thread = new Thread(()->{synchronized (locker){for (int i = 0; i < 5000; i++) {count++;}}});Thread thread1 = new Thread(()->{synchronized (locker){for (int i = 0; i < 5000; i++) {count++;}}});thread.start();thread1.start();thread.join();thread1.join();System.out.println("count = "+count);}
}

在画个图对比一下:

相关文章:
Java线程 - 详解(2)
一,线程安全问题 有些代码在单个线程的环境下运行,完全正确,但是同样的代码,让多个线程去执行,此时就可能出现BUG,这就是所谓的 "线程安全问题"。举一个例子: public class Demo {s…...
事务特性 - 达梦数据库
达梦数据库事务特性 1 事务特性1.1 原子性1.2 一致性1.3 隔离性1.4 持久性 1 事务特性 事务必须具备什么属性才是一个有效的事务呢?一个逻辑工作单元必须表现出四种属性,即原子性、一致性、隔离性和持久性,这样才能成为一个有效的事务。DM 数…...
axios 使用FormData格式发送GET请求
如果你需要使用,FormData格式,发送GET请求 将参数拼接到 FormData对象 中,使用 URLSearchParams 将FormData对象转换为查询参数字符串,并将其拼接到URL中,这样就能以FormData格式发送GET请求给服务器 注意࿱…...
CS144(2023 Spring)Lab 1: stitching substrings into a byte stream
文章目录 前言其他笔记相关链接 1. Getting started2. Putting substrings in sequence2.1 需求分析2.2 注意事项2.3 代码实现 3. 测试与优化 前言 这一个Lab主要是实现一个TCP receiver的字符串接收重组部分。 其他笔记 Lab 0: networking warmup Lab 1: stitching substri…...
【PHP】常用的PHP内置函数
1、PHP内置函数非常丰富,用于执行各种任务。以下是一些常用的PHP内置函数: 字符串操作函数: strlen(): 返回字符串的长度。 strpos(): 查找字符串中的某个子串第一次出现的位置。 substr(): 返回字符串的子串。 str_replace(): 替换字符串中的…...
css自学框架之消息弹框
首先我们还是看看消息弹框效果: 主要实现代码分为三部分 一、CSS部分,这部分主要是定义样式,也就是我们看到的外表,主要代码: /* - 弹窗 */notice{top: 0;left: 0;right: 0;z-index: 10;padding: 1em;position: fix…...
42、Flink 的table api与sql之Hive Catalog
Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...
PAT 1145 Hashing - Average Search Time
个人学习记录,代码难免不尽人意。 The task of this problem is simple: insert a sequence of distinct positive integers into a hash table first. Then try to find another sequence of integer keys from the table and output the average search time (the…...
C++调用Python Win10 Miniconda虚拟环境配置
目录 前言1. Win10 安装 Miniconda2. 创建虚拟环境3. 配置C调用python环境4. C调用Python带参函数5.遇到的问题6. 总结 前言 本文记录了Win10 系统下Qt 应用程序调用Python时配置Miniconda虚拟环境的过程及遇到的问题,通过配置Python虚拟环境,简化了Qt应…...
从0到1学会Git(第一部分):Git的下载和初始化配置
1.Git是什么: 首先我们看一下百度百科的介绍:Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。 也是Linus Torvalds为了帮助管理Linux内核开发而开发的一个开放源码的版本控制软件。 …...
【记录】手机QQ和电脑QQ里的emoji种类有什么差异?
版本 手机 QQ:V 8.9.76.12115 电脑 QQ:QQ9.7.15(29157) 偶然发现,有一种emoji手机上怎么找都找不到,一开始以为自己失忆了,后来发现这种emoji只在电脑上有。 接下来简单说一下找emoji差异的方式…...
blender界面认识01
学习视频 【基础篇】1.2 让手听话_哔哩哔哩_bilibili 目录 控制视角 控制物体 选择对象1 小结 控制视角 长按鼠标中键-----视角旋转 shift鼠标中键-----视角平移 滚动鼠标中键-----视角缩放 也可以通过界面的快捷工具实现 这个视角旋转有一点像catia中罗盘,…...
TCP数据报结构分析(面试重点)
在传输层中有UDP和TCP两个重要的协议,下面将针对TCP数据报的结构进行分析 关于UDP数据报的结构分析推荐看UDP数据报结构分析(面试重点) TCP结构图示 TCP报头结构的分析 一.16位源端口号 源端口表示发送数据时,发送方的端口号&am…...
合并两个有序的单链表,合并之后的链表依然有序
定义节点 class ListNode {var next: ListNode _var x: Int _def this(x: Int) {thisthis.x x}override def toString: String s"x>$x" } 定义方法 class LinkedList {var head new ListNode(0)def getHead(): ListNode this.headdef add(listNode: Li…...
eureka迁移到nacos--双服务中心注册
服务注册中心的迁移有多种方式,官网使用nacos sync,还有民间开发的双注册中心组件eureka-nacos-proxy,但是我用了不太顺利,所以用的是阿里巴巴的双注册中心组件edas-sc-migration-starter spring boot:2.5.3 引入依赖 …...
线程池使用不规范导致线程数大以及@Async的规范使用
文章详细内容来自:线程数突增!领导:谁再这么写就滚蛋! 下面是看完后文章的,一个总结 线程池的使用不规范,导致程序中线程数不下降,线程数量大。 临时变量的接口,通过下面简单的线…...
启莱OA treelist.aspx SQL注入
子曰:“为政以德,譬如北辰,居其所,而众星共之。” 漏洞复现 访问漏洞url: 使用SQLmap对参数 user 进行注入 漏洞证明: 文笔生疏,措辞浅薄,望各位大佬不吝赐教,万分感…...
ES是一个分布式全文检索框架,隐藏了复杂的处理机制,核心数据分片机制、集群发现、分片负载均衡请求路由
ES是一个分布式框架,隐藏了复杂的处理机制,核心数据分片机制、集群发现、分片负载均衡请求路由。 ES的高可用架构,总体如下图: 说明:本文会以pdf格式持续更新,更多最新尼恩3高pdf笔记,请从下面…...
xml和json互转工具类
分享一个json与xml互转的工具类,非常好用 一、maven依赖 <!-->json 和 xm 互转</!--><dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version></dependency&g…...
Windows系统下MMDeploy预编译包的使用
Windows系统下MMDeploy预编译包的使用 MMDeploy步入v1版本后安装/使用难度大幅下降,这里以部署MMDetection项目的Faster R-CNN模型为例,将PyTorch模型转换为ONNX进而转换为Engine模型,部署到TensorRT后端,实现高效推理,…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...
大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...
OPENCV图形计算面积、弧长API讲解(1)
一.OPENCV图形面积、弧长计算的API介绍 之前我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天我们主要结合轮廓检测的API去计算图形的面积,这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能,常用的API…...
