并发编程 - 线程同步(一)
经过前面对线程的尝试使用,我们对线程的了解又进一步加深了。今天我们继续来深入学习线程的新知识 —— 线程同步。

01、什么是线程同步
线程同步是指在多线程环境下,确保多个线程在同时使用共享资源时不会发生冲突或数据不一致问题的技术,保证线程间的正确协作。它的目的是使得多个线程在执行过程中能够按照某种顺序、安全地使用共享资源。
02、为何需要线程同步
1、避免竞争条件
不知道大家还记得在《并发编程 - 初识线程》中出现的关键字volatile和特性ThreadStatic吗?它们都是为了解决多线程共享资源问题。
在多线程中当多个线程需要同时使用共享资源时,很容易产生互相竞争资源使用权的情况,这一问题也叫竞争条件。此时就可以通过线程同步技术实现多个线程按顺序使用共享资源,从而避免竞争条件。
2、保证共享资源安全
我们举个简单的例子,假如我的银行账户里有1000元,此时我正在用电子银行在线上操作准备向我老婆的账户里转账100元,而恰巧此时我老婆拿着我的银行卡准备取款500。
假如银行系统还是一个只有多线程,没有线程同步功能的老系统,在这一前置条件下。假如恰巧我们俩在同一瞬间点了确认操作,相信此时系统会发生什么?
有可能会是系统同时收到我们俩的请求,此时我的操作线程A,首先读取我账户余额1000,然后执行转账操作把余额减100得到900,再更新至余额中。而我老婆的操作线程B因为是和我同时的,所以在读取我账户余额的时候得到的也是1000,而不是900,此时线程B执行取款500操作把余额减500得到500,再更新至余额中。
可以发现我们俩最后更新余额,无论谁更新成功最后结果都是不正确的。这个例子就导致银行账户余额最终不正确,也就是我们说的共享资源不安全。如果使用线程同步,使得线程A、B可以按顺序执行,无论谁先执行最终结果都会是正确的。
下面我们再来结合代码举一个经典问题 —— torn read。
先解释一下什么叫torn read,可以翻译成一次读取被撕成两半。或者说在机器级别上,要分两个MOV指令才能读完。
具体来说就是一个long类型变量_var,当一个线程把_var赋值为0x0123456789ABCDEF,而此时另一个线程来读取_var,结果读取的值是0x0123456700000000或0x0000000089ABCDEF。这同样是因为多线程导致的共享资源不安全问题。
下面看看模拟代码实现效果:
public class ThreadSync
{//共享的int64变量public static long _var; public static void Run(){//启动写入线程var writerThread = new Thread(WriteToSharedValue);//启动读取线程var readerThread = new Thread(ReadFromSharedValue);//启动线程writerThread.Start();readerThread.Start();//等待线程执行完成writerThread.Join();readerThread.Join();}//写入线程static void WriteToSharedValue(){//模拟分两步写入long high = 0x01234567;long low = 0x89ABCDEF;unsafe{//将 _var 分成高低两部分写入//写高 32 位_var = high << 32;// 确保读取线程能在这里读取中间值Thread.Sleep(0); //写低 32 位_var |= low;}Console.WriteLine($"写: 写入值 0x{_var:X16}");}//读取线程static void ReadFromSharedValue(){// 读取共享变量的值Console.WriteLine($"读: 读取值 0x{_var:X16}");}
}
我们看下执行效果:

当然上面的例子并不是每次都会出现的,可能需要多运行几次,另外关于写入线程为什么不是直接赋值而是把值拆成高低位分两次写入?
这是因为我的电脑是64位系统,在大多数现代的 x64 系统架构(例如 Intel 和 AMD 处理器)上,64 位的原子性操作通常是被保证的。即使对于像 long(64 位)这种数据类型,处理器通常会在硬件层面确保它的读写操作是原子性的,因此,不太容易发生撕裂的读(torn read)。
所以这里的代码把一次赋值行为认为拆解成两步,同时Thread.Sleep(0)也为了让当前线程主动让出 CPU 时间片,使读线程有机会读取,使其更贴近在x32环境下运行的情况。如果有条件可以用直接赋值再x32环境下看看效果。
03、如何实现线程同步
1、避免资源共享
当然严格意义上说可能这一条不算是线程同步,只能说解决了多线程碰到的问题,达到线程同步的效果。
如果没有共享资源,那么自然就无须进行线程同步。大多数时候可以通过重新设计程序来除移共享状态,从而去掉复杂的同步构造。尽可能避免在多个线程间使用单一对象。
除了通过重新设计来移除共享状态,还可以通过语言特性设计使其达到无共享状态。比如值类型在传递过程中总是被复制,每个线程都会有自己的数据副本,比如看下面这个方法:
public static int Max(int val1, int val2)
{ return val1 > val2 ? val1 : val2;
}
即使这个方法没有使用任何线程同步方法,这个方法也是线程安全的。因为值类型特性原因,所以传给Max的两个int值会复制到方法内部,形成自己的数据副本。此时无论有多少个线程调用Max方法,每个线程处理的都是它自己的数据,线程之间并不会互相干扰。
2、用户模式同步机制
用户模式同步机制指在用户空间内完成线程的阻塞和唤醒操作,由程序自己管理同步对象的一种同步方式,因为不涉及与操作系统内核交换,因此开销较低,更轻量级。
实现方式有SpinLock、SpinWait、Monitor(lock)等。

3、内核模式同步机制
内核模式同步机制是指在操作系统内核空间就完成线程的挂起与恢复,由操作系统管理同步对象的一种同步方式,因为每次线程同步操作都需要操作系统参与,因此必然回涉及内核态的上下文切换,同时还是涉及到操作系统内部的数据结构和资源管理,因此内核模式同步机制往往会导致较高的开销。
实现方式有Semaphore、Mutex、AutoResetEvent等。
4、混合模式同步机制
混合模式同步机制在某些情况下会根据线程竞争的情况在用户模式和内核模式之间切换。通常,当资源访问冲突较小或线程阻塞较少时,采用用户模式同步;当资源争用较多或有较大的线程等待时,自动切换到内核模式同步。
实现方式有SemaphoreSlim、ManualResetEventSlim、CountDownEvent、Barrier、ReaderWriterLockSlim等。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
相关文章:
并发编程 - 线程同步(一)
经过前面对线程的尝试使用,我们对线程的了解又进一步加深了。今天我们继续来深入学习线程的新知识 —— 线程同步。 01、什么是线程同步 线程同步是指在多线程环境下,确保多个线程在同时使用共享资源时不会发生冲突或数据不一致问题的技术,保…...
PyTorch入门 - 为什么选择PyTorch?
PyTorch入门 - 为什么选择PyTorch? Entry to PyTorch - Why PyTorch? by JacksonML $ pip install pytorch安装完毕后,可以使用以下命令,导入第三方库。 $ import pytorch...
leetcode刷题记录(八十六)——84. 柱状图中最大的矩形
(一)问题描述 84. 柱状图中最大的矩形 - 力扣(LeetCode)84. 柱状图中最大的矩形 - 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾…...
Android GLSurfaceView 覆盖其它控件问题 (RK平台)
平台 涉及主控: RK3566 Android: 11/13 问题 在使用GLSurfaceView播放视频的过程中, 增加了一个播放控制面板, 覆盖在视频上方. 默认隐藏setVisibility(View.INVISIBLE);点击屏幕再显示出来. 然而, 在RK3566上这个简单的功能却无法正常工作. 通过缩小视频窗口可以看到, 实际…...
开源鸿蒙开发者社区记录
lava鸿蒙社区可提问 Laval社区 开源鸿蒙项目 OpenHarmony 开源鸿蒙开发者论坛 OpenHarmony 开源鸿蒙开发者论坛...
【Linux网络编程】传输层协议
目录 一,传输层的介绍 二,UDP协议 2-1,UDP的特点 2-2,UDP协议端格式 三,TCP协议 3-1,TCP报文格式 3-2,TCP三次握手 3-3,TCP四次挥手 3-4,滑动窗口 3-5…...
10个非常基础的 Javascript 问题
Javascript是一种用于Web开发的编程语言。JavaScript在网络的客户端上运行。 根据MDN,JavaScript(通常缩写为JS)是一种轻量级的,解释性的,面向对象的语言,具有一流的功能,并且最著名的是Web页面…...
Mysql索引(学习自用)
目录 一、索引概述 优缺点 二、索引结构 1、索引数据结构 2、索引支持结构 3、B树 4、B树 5、hash索引 6、为啥采用B树索引 三、索引分类 四、索引语法 五、索引性能分析 5.1查看执行频率 5.2慢查询日志 5.3profiling 5.4explain 六、索引使用规则 6.1验证索…...
eniops库中reduce函数使用方法
reduce 是 eniops 中的一个常用函数,用于对张量进行降维操作。它允许你通过指定维度名称和操作类型(如求和、均值等)来简化张量的形状。 import eniops import torch# 创建一个示例张量 x torch.randn(2, 3, 4)# 使用 reduce 进行降维操作 …...
阴沟翻船题——Longest Substring Without Repeating Characters
一、事件概述 今天接到一个面试,让线上做题。面试官出了个leetcode的题。题目如图所示: 我没有刷过leetcode,上学时候我们做的hdu-acm和codeforces。咋一接到题目,看到是个字符串题,并且找最长字串,第一反…...
Jetpack Compose 和 Compose Multiplatform 还有 KMP 的关系
今天刚好看到官方发布了一篇文章,用于讨论 Compose Multiplatform 和 Jetpack Compose 之间的区别,突然想起之前评论区经常看到说 “Flutter 和 CMP 对于 Google 来说项目重叠的问题”,刚好可以放一起聊一聊。 最近写的几篇内容写的太干&…...
微信小程序中实现背景图片完全覆盖显示,可以通过设置CSS样式来实现
wxml页面代码 <view class"beijing"></view>wxss样式代码 /* pages/beiJing/beiJing.wxss */ .beijing {background-image: url("https://www.qipa250.com/qipa.jpg");/* 定位:绝对定位 */position: absolute;/* 上下左右都定位到…...
【0x0012】HCI_Delete_Stored_Link_Key命令详解
目录 一、命令参数 二、命令格式及参数 2.1. HCI_Delete_Stored_Link_Key 命令格式 2.2. BD_ADDR 2.3. Delete_All 三、生成事件及参数 3.1. HCI_Command_Complete事件 3.2. Status 3.3. Num_Keys_Deleted 四、命令执行流程 4.1. 命令发送阶段 4.2. 控制器处理阶段…...
console的各种方法
console除了常用的log方法,还有很多方便的方法。 console.table 表格 将数据以表格形式展示 console.group 分组 console.group、console.groupEnd:开启、结束分组,使结构更加清晰 console.dir 对象 打印函数或dom时,log无法打…...
spring boot关于系统首页自动跳转拼接到index
业务说明 通过http://localhost:8091访问服务器时,会动态的跳转到系统的欢迎页面. 实现原理: 说明程序启动时会自动的加载一个默认的请求路径(url:http://localhost:8091/) index 之后动态的拼接前缀和后缀. /WEB-INF/views/index.jsp...
指针生成网络(PGN)详细指南(引入)
一、Seq2Seq模型:编码-解码框架的开山之作 我们首先要了解的是seq2seq(Sequence-to-Sequence)模型。它最早由Google在2014年的一篇论文中提出,是第一个真正意义上的端到端的编码器-解码器(Encoder-Decoder)…...
案例研究丨浪潮云洲通过DataEase推进多维度数据可视化建设
浪潮云洲工业互联网有限公司(以下简称为“浪潮云洲”)成立于2018年,定位于工业数字基础设施建设商、具有国际影响力的工业互联网平台运营商、生产性互联网头部服务商。截至目前,浪潮云洲工业互联网平台连续五年入选跨行业跨领域工…...
k8s 蓝绿发布、滚动发布、灰度发布
在Kubernetes(k8s)中,蓝绿发布、滚动发布、灰度发布(金丝雀发布)是三种常见的应用部署和更新策略。下面将分别对这几种发布方式进行说明,并给出相应的例子。 蓝绿发布 蓝绿发布是一种无缝切换版本的部署策…...
UWB原理:AOA测角原理Angel of Arrival
AOA测角原理Angel of Arrival 准备工作: UWB默认使用channel=9,Frequency = 7987.2mMhz,约8GHz。 波长 天线RX1, RX2间距一般为20mm左右,假如发射端TX离2个RX距离2m=2000mm,大约是100倍天线间距。2个入射角可以近似相同。 测角原理: <...
plus.runtime.install在android10无效
在 Android 10 中,使用 plus.runtime.install 方法来进行动态安装应用或进行其他操作可能会失效。这是因为从 Android 10 开始,操作系统在安全性和隐私方面做了很多改进,特别是与应用安装相关的权限变更。 在 Android 10(API 级别…...
像素剧本圣殿详细步骤:如何重置时空+保存平行宇宙创作记录
像素剧本圣殿详细步骤:如何重置时空保存平行宇宙创作记录 1. 认识像素剧本圣殿 像素剧本圣殿是一款基于Qwen2.5-14B-Instruct深度微调的专业剧本创作工具。它将强大的AI推理能力与独特的8-Bit复古美学相结合,为创作者提供了一个沉浸式的剧本开发环境。…...
Kandinsky-5.0-I2V-Lite-5s图生视频实战教程:5秒短视频一键生成(RTX4090D友好)
Kandinsky-5.0-I2V-Lite-5s图生视频实战教程:5秒短视频一键生成(RTX4090D友好) 1. 快速认识Kandinsky-5.0-I2V-Lite-5s Kandinsky-5.0-I2V-Lite-5s是一款专为短视频创作设计的轻量级AI模型。它最大的特点就是简单高效——你只需要准备一张起…...
MCP协议实战:用npx免安装部署文件系统服务的完整指南
MCP协议实战:用npx免安装部署文件系统服务的完整指南 在当今快速迭代的开发环境中,如何高效部署和管理文件系统服务成为许多开发者面临的挑战。传统方式往往需要全局安装各种工具包,不仅占用系统资源,还可能引发版本冲突。本文将带…...
基于BANG语言的Sigmoid算子开发与PyTorch集成实战指南
1. BANG语言与Sigmoid算子开发基础 第一次接触寒武纪BANG语言时,我被它类似CUDA但更简洁的语法设计惊艳到了。这种专为MLU硬件设计的异构编程语言,通过在C/C基础上扩展并行计算特性,让开发者能更高效地利用寒武纪芯片的算力资源。 BANG核心语…...
新疆某工程围岩等级,包含以下7列,均为数值型数据
一、文件结构 文件包含1个工作表:Sheet1仅 Sheet1 包含数据,其余为空表。二、数据列说明 Sheet1 包含以下7列,均为数值型数据:列名(英文)列名(中文推测)数据类型说明VPR未知参数1数值…...
G-Helper终极指南:华硕笔记本轻量级控制工具完全教程
G-Helper终极指南:华硕笔记本轻量级控制工具完全教程 【免费下载链接】g-helper Lightweight, open-source control tool for ASUS laptops and ROG Ally. Manage performance modes, fans, GPU, battery, and RGB lighting across Zephyrus, Flow, TUF, Strix, Sca…...
HP20x气压传感器Arduino驱动深度解析
1. Grove Barometer HP20x 高精度气压/温度/海拔传感器驱动深度解析1.1 项目定位与工程价值Grove Barometer HP20x 是 Seeed Studio 推出的基于 HP206C(或兼容型号 HP203B/HP202C)高精度气压传感芯片的模块化传感器。该驱动库并非简单封装,而…...
AD09 PCB设计核心技巧与实战经验
1. PCB设计基础与AD09平台概述作为一名从业超过十年的硬件工程师,我使用过从Protel 99到Altium Designer 21的各种版本,其中AD09(Altium Designer 2009)因其稳定性和适中的硬件要求,至今仍是许多工程师的首选工具。PCB…...
雷军5小时拆车直播爆火!硬核技术成新风口,自媒体可直接做
4月2日晚,雷军5小时直播拆解新一代SU7引发全网热议,单场观看量突破1亿,弹幕满是“硬核”“专业”的好评。这场直播颠覆了技术内容的传播模式,从“参数堆砌”转向“实证拆解”,从“单向宣讲”升级为“双向互动”&#x…...
轻量级抢占式任务调度器:面向Arduino的毫秒级实时调度
1. 项目概述Task Scheduler是一款专为 Atmel AVR(ATmega328P/ATmega2560)与 ARM Cortex-M3(SAM3X8E)架构微控制器设计的轻量级、抢占式实时任务调度器,面向 Arduino 生态系统深度优化。其核心目标并非替代完整 RTOS&am…...
