并发编程 - 线程同步(二)
经过前面对线程同步初步了解,相信大家对线程同步已经有了整体概念,今天我们就来一起看看线程同步的具体方案。
01、ThreadStatic
严格意义上来说这两个并不是实现线程同步方案,而是解决多线程资源安全问题,而我们研究线程同步最终也是为了解决多线程资源安全问题,因此就先说下这两个用法。
ThreadStatic特性可以实现线程本地存储,使得每个线程都有一个独立的字段副本。从而避免不同线程间共享资源。
使用ThreadStatic时需要注意以下几点:
1、ThreadStatic仅能作用于静态字段;。
2、ThreadStatic字段不应使用内联初始化。
3、每个线程都会有独立的_threadLocalVariable实例,当线程退出时,相关的线程本地存储会被清除。
4、由于 ThreadStatic 是线程局部存储,它并不是跨线程共享数据的解决方案。
使用起来也很简单,我们来着重说说上面注意点的第二点,虽然语法上可以写出内联初始化,但是这样会导致一个问题:仅有访问其的首个线程上可以获取其初始化变量值,而其他所有线程都只能获取到变量类型的默认值。比如下面这段代码:
[ThreadStatic]
public static int _threadStaticValue = 1;
public static void ThreadStaticRun()
{var thread1 = new Thread(ThreadStatic1);var thread2 = new Thread(ThreadStatic2);var thread3 = new Thread(ThreadStatic3);thread1.Start();thread2.Start();thread3.Start();
}
static void ThreadStatic1()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadStaticValue}");
}
static void ThreadStatic2()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadStaticValue}");
}
static void ThreadStatic3()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadStaticValue}");
}
也就是上面代码只有一个线程能打印出1,其他线程都只能打印出0,我们看看实际打印结果:
因此注意项第二点提出ThreadStatic字段不应使用内联初始化,因为这样并不能保证每个线程都能获取到相同的初始值。
也因为ThreadStatic有这个缺陷所以引出了ThreadLocal。
02、ThreadLocal
可以说ThreadLocal功能和ThreadStatic完全一样,并且还解决了其缺陷,因此更推荐使用ThreadLocal。
可以使用 System.Threading.ThreadLocal 类型创建一个基于实例的线程本地变量,该变量由你提供的 Action 委托在所有线程上进行初始化。如下示例中,访问_threadLocalValue的所有线程都可以获取到初始化值1。
private static ThreadLocal<int> _threadLocalValue = new ThreadLocal<int>(() => 1);
public static void ThreadLocalRun()
{var thread1 = new Thread(ThreadLocal1);var thread2 = new Thread(ThreadLocal2);var thread3 = new Thread(ThreadLocal3);thread1.Start();thread2.Start();thread3.Start();
}
static void ThreadLocal1()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadLocalValue.Value}");
}
static void ThreadLocal2()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadLocalValue.Value}");
}
static void ThreadLocal3()
{Console.WriteLine($"线程 Id : {Environment.CurrentManagedThreadId},变量值:{_threadLocalValue.Value}");
}
执行结果如下:
并且可以通过ThreadLocal.Value 属性进行读取和写入,也就是通过_threadLocalValue.Value对变量进行赋值和取值。
03、volatile关键字
首先volatile关键字同样不是一个完整的线程同步机制,其主要作用是防止缓存和防止编译器优化。
在C#语言开发中,由于编译器优化、JIT 编译、硬件缓存以及内存重排序等行为,很容易使得程序出现并发错误,尤其在多线程环境下这些情况会更为明显。虽然这些优化是在不影响程序逻辑的情况下进行的,但是因为重新排序对内存的读取和写入,进而可能导致数据竞争和同步问题。
volatile关键字就是为了告诉编译器和运行时:该字段的值可能会被多个线程同时修改,因此每次访问该字段时,都应该直接从主内存中读取,而不是使用寄存器或缓存中的值。这样可以防止 CPU 的优化行为导致某些线程读取到过时的值。
我们一起看看如下代码:
//控制线程的标志
private static bool _flag = false;
//计数器
private static int _counter = 0;
public static void VolatileRun()
{var thread1 = new Thread(Volatile1);var thread2 = new Thread(Volatile2);thread1.Start();thread2.Start();thread1.Join();thread2.Join();//Console.WriteLine($"计数器最后的值: {counter}");
}
static void Volatile1()
{//注意:以下两行代码可能按相反的顺序执行//设置计数器_counter = 88;//线程1:设置标志位,并且增加计数器_flag = true;
}
static void Volatile2()
{//注意:_counter可能优先于_flag读取//线程2:等待标志位变为 true,然后读取计数器//等待 _flag 被设置为 truewhile (!_flag) ;//打印计数器值Console.WriteLine($"当前计数器的值: {_counter}");
}
上面的代码很难在复现下面要说的问题,因此下面仅以此代码作为示例讲解。
上面代码的问题在于,经过编译器优化和内存重排序后, Volatile1线程中的两行赋值代码可能被颠倒了顺序,如果从单线程角度来说这个顺序颠倒无关紧要,最总结果都是_counter被赋值了88,_flag被赋值了true。但是在多线程环境下,对于Volatile2线程来说就完全不一样了,此时却先读取到_flag为true,然后打印_counter为0,和预期完全不一样。
我们再从另一个角度来说,假定Volatile1线程中的代码安装编码顺序执行了,没有被优化。在编译Volatile2线程中的代码时,编译器必须生成代码将_flag和_counter从RAM(主存)中读入CPU寄存器,此时RAM可能先读入_counter的值,为0。与此同时Volatile1线程可能执行,将_counter修改为88,想_flag修改为true。此时Volatile2线程的CPU寄存器还没有看到_counter已被Volatile1线程修改为88,然后继续将_flag的值从RAM中读入CPU寄存器,但是由于此时_flag已经被Volatile1线程修改为true,所以最后Volatile2线程同样会打印_counter为0。
开发时很容易忽略这些细微之处,并且由于开发调试环境不会进行代码优化,就导致问题往往到了生产环境下才显现出来。
为了解决这个问题我们就可以使用volatile关键字了。对于被声明为volatile的字段将从编译器优化、JIT 编译、硬件缓存以及内存重排序等优化中排除,使用也很简单,可以如下使用:
private static volatile bool _flag = false;
另外volatile关键字不能引用于double,long,数组等类型,可以使用Volatile.Read和Volatile.Write静态方法来完成。
同时volatile关键字虽然可以解决许多并发问题,但是因为其不是原子操作,因此它并不能算是一个完整的线程同步机制,因此在多线程环境下还是需要借助一些其他同步机制来保证线程安全。
因此volatile最大的应用场景就是在需要保证多个线程访问同一个共享变量时,大家都可以立刻看到最新的值,尤其是不涉及复杂操作如递增递减等。
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner
相关文章:
并发编程 - 线程同步(二)
经过前面对线程同步初步了解,相信大家对线程同步已经有了整体概念,今天我们就来一起看看线程同步的具体方案。 01、ThreadStatic 严格意义上来说这两个并不是实现线程同步方案,而是解决多线程资源安全问题,而我们研究线程同步最终…...
仿知乎网站问答源码,开源版
按照需要一定动手能力 发文章,发视频,发想法,提问回答,注册登录 开发环境 使用技术:springbootthymeleafRedis; 开发环境:tomcat8.0,jdk8.0, IDEA项目,mysql5.7 源码下…...
CTF-web: phar反序列化+数据库伪造 [DASCTF2024最后一战 strange_php]
step 1 如何触发反序列化? 漏洞入口在 welcome.php case delete: // 获取删除留言的路径,优先使用 POST 请求中的路径,否则使用会话中的路径 $message $_POST[message_path] ? $_POST[message_path] : $_SESSION[message_path]; $msg $userMes…...
阿里:基于路由和规划的多agent系统
📖标题:Talk to Right Specialists: Routing and Planning in Multi-agent System for Question Answering 🌐来源:arXiv, 2501.07813 🌟摘要 🔸利用大型语言模型(LLM),…...
Java基础面试题总结(题目来源JavaGuide)
问题1:Java 中有哪 8 种基本数据类型?它们的默认值和占用的空间大小知道不? 说说这 8 种基本数据类型对 应的包装类型。 在 Java 中,有 8 种基本数据类型(Primitive Types): 基本数据类型关键…...
CSS(快速入门)
欢迎大家来到我的博客~欢迎大家对我的博客提出指导,有错误的地方会改进的哦~点击这里了解更多内容 目录 一、什么是CSS?二、基本语法规范三、CSS选择器3.1 标签选择器3.2 id选择器3.3 class选择器3.4 通配符选择器3.5 复合选择器 四、常用CSS样式4.1 color4.2 font…...
架构技能(六):软件设计(下)
我们知道,软件设计包括软件的整体架构设计和模块的详细设计。 在上一篇文章(见 《架构技能(五):软件设计(上)》)谈了软件的整体架构设计,今天聊一下模块的详细设计。 模…...
2. Java-MarkDown文件解析-工具类
2. Java-MarkDown文件解析-工具类 1. 思路 读取markdown文件的内容,根据markdown的语法进行各个类型语法的解析。引入工具类 commonmark 和 commonmark-ext-gfm-tables进行markdown语法解析。 2. 工具类 pom.xml <!-- commonmark 解析markdown --> <d…...
OpenEuler学习笔记(十五):在OpenEuler上搭建Java运行环境
一、在OpenEuler上搭建Java运行环境 在OpenEuler上搭建Java运行环境可以通过以下几种常见方式,下面分别介绍基于包管理器安装OpenJDK和手动安装Oracle JDK的步骤。 使用包管理器安装OpenJDK OpenJDK是Java开发工具包的开源实现,在OpenEuler上可以方便…...
【Rust自学】15.4. Drop trait:告别手动清理,释放即安全
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 15.4.1. Drop trait的意义 类型如果实现了Drop trait,就可以让程序员自定义当值…...
深入探讨Web应用开发:从前端到后端的全栈实践
在数字化时代,Web应用已成为连接用户与服务的关键桥梁。无论是电商平台、社交媒体,还是企业内部管理系统,Web应用都扮演着不可或缺的角色。本文将深入探讨Web应用开发的全栈实践,从前端的用户体验设计到后端的数据处理与存储&…...
4.贪心算法
贪心 贪心算法(Greedy Algorithms)是 C 等编程语言中常用的一种算法策略。 定义 贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最…...
js笔记(黑马程序员)
(Web APIs day4) 一、日期对象 1.实例化 在代码中发现了 new 关键字时,一般将这个操作称为实例化 创建一个时间对象并获取时间// 1.得到当前时间 2.日期对象方法 因为日期对象返回的数据我们不能直接使用,所以需…...
LangChain:使用表达式语言优化提示词链
在 LangChain 里,LCEL 即 LangChain Expression Language(LangChain 表达式语言),本文为你详细介绍它的定义、作用、优势并举例说明,从简单示例到复杂组合示例,让你快速掌握LCEL表达式语言使用技巧。 定义 …...
Python 有用的资源
Python 有用的资源 引言 Python 作为一种强大的编程语言,因其简洁明了的语法和丰富的库资源,在数据分析、人工智能、网络开发等领域拥有广泛的应用。对于初学者和专业人士来说,掌握一些有用的Python资源可以大大提高编程效率。本文将为您介绍一些实用的Python资源,帮助您…...
汽车蓝牙钥匙定位仿真小程序
此需求来自于粉丝的真实需求,假期没事,牛刀小试。 一、项目背景 如今,智能车钥匙和移动端定位技术已经相当普及。为了探索蓝牙 Beacon 在短距离定位场景下的可行性,我们搭建了一个简易原型:利用 UniApp 在移动端采集蓝牙信标的 RSSI(信号强度),通过三边定位算法估算钥…...
项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(九)(完结篇)
文章目录 一、成绩查询模块实现1、学生成绩查询功能实现1.1 页面设计1.2 前端页面实现1.3 后端功能实现2、成绩分段查询功能实现2.1 页面设计2.2 前端页面实现2.3 后端功能实现二、试卷练习模块实现三、我的分数模块实现1、 页面设计2、 前端页面实现3、 后端功能实现四、交流区…...
python学opencv|读取图像(四十九)原理探究:使用cv2.bitwise()系列函数实现图像按位运算
【0】基础定义 按位与运算:两个等长度二进制数上下对齐,全1取1,其余取0。 按位或运算:两个等长度二进制数上下对齐,有1取1,其余取0。 按位异或运算: 两个等长度二进制数上下对齐,相…...
electron打包客户端在rk3588上支持h265硬解
目录 前言 chromium是如何支持h265硬解 electron/chromium第一次编译 electron/chromium第二次编译 前言 我们的客户端程序是用electron打包的前端程序,其在rk3588主机上的linux环境运行。之前使用客户端查看h264编码的视频直播是没有问题的,但视频源…...
MyBatis 缓存机制详解
目录 一、什么是缓存? 1. 什么是缓存? 2. 为什么使用缓存? 3. 什么样的数据适合使用缓存? 二、MyBatis 缓存机制 1. 一级缓存(也叫本地缓存) 2. 一级缓存失效的情况 3. 二级缓存 4. 二级缓存失效的…...
【后端开发】字节跳动青训营Cloudwego脚手架
Cloudwego脚手架使用 cwgo脚手架 cwgo脚手架 安装的命令: GOPROXYhttps://goproxy.cn/,direct go install github.com/cloudwego/cwgolatest依赖thriftgo的安装: go install github.com/cloudwego/thriftgolatest编辑echo.thrift文件用于生成项目&…...
数据结构选讲 (更新中)
参考 smWCDay7 数据结构选讲2 by yyc 。 可能会补充的: AT_cf17_final_j TreeMST 的 F2 Boruvka算法 目录 AT_cf17_final_j Tree MST AT_cf17_final_j Tree MST link 题意 给定一棵 n n n 个点的树,点有点权 w i w_i wi,边有边权。建立…...
springboot 简化 spring开发
什么是自动配置? 简单概念: Spring Boot 自动配置是一种 “约定优于配置” 的做法。根据项目类路径(classpath)上存在的依赖、配置文件中的某些属性,Spring Boot 会自动为常见场景创建并配置相关 Bean,省…...
Yolo11 + OCR 营业执照识别+信息抽取(预期后续改用其他ocr更简单,推理预计使用onnxruntim加速,分c++和python两种方式部署)
目录 一 数据集制作 1 labelimg的安装与使用 2 标注方式 3 数据集制作 二 模型训练 三 使用Yolo11 + OCR 实现“营业执照”信息解析完整方案 1 cutLinesforcode.py 2 getBusinessLicenseContentPart.py 3 getPartWords.py 4 pdfTojpg.py 5 main.py 本项目可用于毕业…...
机器学习day3
自定义数据集使用框架的线性回归方法对其进行拟合 import matplotlib.pyplot as plt import torch import numpy as np # 1.散点输入 # 1、散点输入 # 定义输入数据 data [[-0.5, 7.7], [1.8, 98.5], [0.9, 57.8], [0.4, 39.2], [-1.4, -15.7], [-1.4, -37.3], [-1.8, -49.1]…...
C基础寒假练习(3)
一、求数组中的第二大值 #include <stdio.h> int main() {int arr[] {12, 35, 1, 10, 34, 1};int size sizeof(arr) / sizeof(arr[0]);if (size < 2) {printf("数组元素不足两个\n");return 0;}int first -2147483648, second -2147483648; // 使用IN…...
星际战争模拟系统:新月的编程之道
星际战争模拟系统:新月的编程之道 作为一名在 25 世纪星际时代成长起来的科学家和军事战略家,我对编程和人工智能的热爱始于童年。我的父亲是一位著名的物理学家,母亲是一位杰出的生物工程师。在他们的影响下,我从小就对科学和技术…...
【Redis】 String 类型的介绍和常用命令
1. 介绍 Redis 中的 key 都是字符串类型Redis 中存储字符串是完全按照二进制流的形式保存的,所以 Redis 是不处理字符集编码的问题,客户端传入的命令中使用的是什么编码就采用什么编码,使得 Redis 能够处理各种类型的数据,包括文…...
U盘打开提示格式化:深度解析与数据恢复全攻略
在数字化时代,U盘作为便捷的数据存储和传输工具,广泛应用于各个领域。然而,当我们满怀期待地插入U盘,却遭遇“U盘打开提示格式化”的尴尬局面时,那份焦虑与无助感油然而生。本文将全面剖析U盘打开提示格式化的原因、应…...
vue2在线生成二维码
亲情提示:如果可以让后端生成就让后端生成 实在不行再前端解决(分享方法只是为了让你快点下班 不是为了让你能者多劳) 创建 npm install qrcodejs2 pnpm install qrcodejs2 <div ref"qrcode" id"myQrCode" class&…...
Linux中的几个基本指令(二)
文章目录 1、cp指令例一:例二:例三:例四:例五: 2、mv 指令例一:例二: 3、cat指令例一: 4、tac指令5、which指令6、date指令时间戳:7、zip指令 今天我们继续学习Linux下的…...
基于Springboot的健身房管理系统【附源码】
基于Springboot的健身房管理系统 效果如下: 系统登陆页面 管理员主页面 器材类型管理页面 健身房管理页面 教练管理页面 用户管理页面 个人信息页面 课程管理页面 研究背景 随着健康意识的不断增强和人们生活水平的提高,健身房已经成为了现代城市中不…...
【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(一)
目录 1 -> 概述 1.1 -> 整体架构 2 -> 文件组织 2.1 -> 目录结构 2.2 -> 文件访问规则 2.3 -> 媒体文件格式 3 -> js标签配置 3.1 -> pages 3.2 -> window 3.3 -> 示例 4 -> app.js 4.1 -> 应用生命周期 4.2 -> 应用对象6…...
Spring Boot多环境配置实践指南
在开发Spring Boot应用时,我们常常需要根据不同的运行环境(如开发环境、测试环境和生产环境)来配置不同的参数。Spring Boot提供了非常灵活的多环境配置机制,通过使用profile-specific properties文件,我们可以轻松地管…...
知识图谱质量评估:构建高质量语义网络的关键
目录 前言1. 知识图谱质量评估的必要性2. 知识图谱质量评估的核心维度2.1 数据层面2.2 结构层面2.3 语义层面2.4 性能层面 3. 知识图谱质量评估的方法3.1 定量评估方法3.2 定性评估方法 4. 知识图谱质量优化建议结语 前言 随着大数据和人工智能技术的飞速发展,知识…...
< OS 有关 > Android 手机 SSH 客户端 app: connectBot
connectBot 开源且功能齐全的SSH客户端,界面简洁,支持证书密钥。 下载量超 500万 方便在 Android 手机上,连接 SSH 服务器,去运行命令。 Fail2ban 12小时内抓获的 IP ~ ~ ~ ~ rootjpn:~# sudo fail2ban-client status sshd Status for the jail: sshd …...
Unity游戏(Assault空对地打击)开发(1) 创建项目和选择插件
目录 前言 创建项目 插件导入 地形插件 前言 这是游戏开发第一篇,进行开发准备。 创作不易,欢迎支持。 我的编辑器布局是【Tall】,建议调整为该布局,如下。 创建项目 首先创建一个项目,过程略,名字请勿…...
SpringBoot 日志
目录 一. 日志概述 二. 日志的使用 1. 打印日志 (1) 获取日志对象 (2) 输出要打印的内容 2. 日志框架简介 (1) 门面模式简介 (2) SLF4J 框架简介 3. 日志的格式 4. 日志的级别 5. 日志配置 (1) 配置日志级别 (2) 日志持久化存储 ① 配置日志文件名 ② 配置日志的…...
每日 Java 面试题分享【第 16 天】
欢迎来到每日 Java 面试题分享栏目! 订阅专栏,不错过每一天的练习 今日分享 3 道面试题目! 评论区复述一遍印象更深刻噢~ 目录 问题一:Java 运行时异常和编译时异常之间的区别是什么?问题二:什么是 Jav…...
《多线程基础之互斥锁》
【互斥锁导读】互斥锁是大家使用最多的线程同步手段,但仅仅知道怎么用还是不够的?比如:面试官问你"互斥锁是属于内核层还是应用层的同步保护机制?性能怎样?","频繁加解锁,会有什…...
渲染流程概述
渲染流程包括 CPU应用程序端渲染逻辑 和 GPU渲染管线 一、CPU应用程序端渲染逻辑 剔除操作对物体进行渲染排序打包数据调用Shader SetPassCall 和 Drawcall 1.剔除操作 视椎体剔除 (给物体一个包围盒,利用包围盒和摄像机的视椎体进行碰撞检测…...
Android车机DIY开发之学习篇(七)NDK交叉工具构建
Android车机DIY开发之学习篇(七)NDK交叉工具构建 1.ubuntu安装GCC sudo apt-get update sudo apt-get install gcc g sudo gcc --version sudo g --version 2.测试GCC VSCODE中新建Hello.c编译 #include <stdio.h> int main(void) { printf(“Hello, this is a progr…...
c++ map/multimap容器 学习笔记
1 map的基本概念 简介: map中所有的元素都是pair pair中第一个元素是key(键),第二个元素是value(值) 所有元素都会根据元素的键值自动排序。本质: map/multimap 属于关联式容器,底…...
计算机网络之计算机网络体系结构
一、定义与概述 计算机网络体系结构是计算机网络及其部件所应该完成功能的精确定义,这些功能由何种硬件或软件完成是遵循这种体系结构的。体系结构是抽象的,实现是具体的,是运行在计算机软件和硬件之上的。 二、主流模型 目前,…...
研发的立足之本到底是啥?
0 你的问题,我知道! 本文深入T型图“竖线”的立足之本:专业技术 技术赋能业务能力。研发在学习投入精力最多,也误区最多。 某粉丝感发展遇到瓶颈,项目都会做,但觉无提升,想跳槽。于是&#x…...
最优化问题 - 内点法
以下是一种循序推理的方式,来帮助你从基础概念出发,理解 内点法(Interior-Point Method, IPM) 是什么、为什么要用它,以及它是如何工作的。 1. 问题起点:带不等式约束的优化 假设你有一个带不等式约束的优…...
Vue5---
目录 一、学习目标 1.自定义指令 2.插槽 3.综合案例:商品列表 4.路由入门 二、自定义指令 1.指令介绍 2.自定义指令 3.自定义指令的语法 三、自定义指令-指令的值 1.需求 2.语法 3.代码示例 五、插槽-默认插槽 1.作用 2.需求 4.使用插槽的基本语法…...
Helm Chart 实战指南
Helm 是 Kubernetes 的包管理工具,而 Helm Chart 是 Helm 的核心概念,用于定义、安装和升级 Kubernetes 应用。本文将带你从零开始,通过实战演练,掌握 Helm Chart 的创建、配置和部署,帮助你高效管理 Kubernetes 应用。 1. 环境准备 在开始之前,确保你已经具备以下环境:…...
如何写一篇高质量的提示词?
不管是产品经理还是使用AI工具的用户,很多时候的烦恼是如何写提示词,我觉得写提示词就是在梳理思路,下边是一个提示词的结果,OpenAI 的总裁 Greg Brockman 曾转发过这个结构。 这种结构可以创建一个清晰、简洁、可执行的提示&…...
系统架构设计师教材:信息系统及信息安全
信息系统 信息系统的5个基本功能:输入、存储、处理、输出和控制。信息系统的生命周期分为4个阶段,即产生阶段、开发阶段、运行阶段和消亡阶段。 信息系统建设原则 1. 高层管理人员介入原则:只有高层管理人员才能知道企业究竟需要什么样的信…...