发布时间:2026/7/5 21:00:53
线程安全介绍
前面我们提到了多线程的概念由于操作系统对线程的调度是随机的抢占式执行。因此在多线程程序中就有可能出现了线程安全问题。1.线程安全问题一段代码如果在多线程并发执行的情况下出现了bug就称为线程安全问题。反之如果一段代码在多线程并发执行的情况下没有出现bug就是线程安全。bug就是代码运行后的实际结果与程序员的预期结果不符合。如以下代码我们的预期结果是100000但是经过我们多次运行之后发现count的值是五花八门的。于是该程序运行的结果与我们的预期结果不符合就说明该程序中有bug有bug就说明代码出现了线程安全问题。public class Demo1 { public static int count0; public static void main(String[] args) throws InterruptedException { Thread t1new Thread(()-{ for(int i0;i50000;i){ count; } }); Thread t2new Thread(()-{ for(int i0;i50000;i){ count; } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); } }上面代码出现线程安全问题的原因分析主要问题出现在count这一行代码上虽然count看起来只有一行代码但是站在CPU的角度涉及到3个指令执行的问题分别为loadadd和save。1. load把内存中count的值加载一份到CPU的寄存器中2. add 将寄存器中count的值进行加1操作3. save最后将修改后的count的值重新存储到内存中由于count操作不是原子性操作指令不只有一条所以在多并发程序中由于程序中多个线程的并发执行可能有线程1刚好执行到load指令线程1的count操作没完全执行就会有线程2开始执行了线程2可能一口气将这三条指令一口气都执行完了线程2完成了一次count操作这时候内存中的count的值就是1。这时线程1就又开始运行了但是由于线程1之前才执行了一个load指令且之前从内存拿到的值是0这时候线程1就有开始执行add和save操作此时线程1才完成了一次count操作此时线程1的寄存器中count的值就是1了然后又将count的值重新存储到了内存中所以此时内存中count的值还是1.这因此就导致经过了两次count的操作然而count的值还是1不是2。如下图上面只是一种情况的分析在多线程并发执行下上面运行的情况就会有很多种因此就导致了线程安全的问题。2.线程安全问题产生的原因在介绍线程安全问题的原因之前我们先了解一个概念原子性。原子性代码的原子性是指一个操作或者一系列操作要么全部执行成功要么全部不执行不会执行部分执行的情况。在多线程并发执行的程序中一个线程中的一个操作或者一系列操作是一个整体不会受到其他线程的操作的影响或者打断这就说明该进程中的操作具有原子性。原因1.根本原因操作系统对于线程的调度是随机的抢占式执行原因2.多个线程同时修改同一个变量原因3.修改操作不是原子的原因4.内存的可见性问题原因5.指令重排序注意事项 一个线程修改一个变量多个线程不是同时修改同一个变量多个线程同时修改不同变量多个线程同时读取同一个变量以上这些情况是不会出现线程安全问题的。3.解决线程安全问题3.1 针对原因1由于操作系统的底层设定我们程序员是无法干预的所以我们无法从该角度解决线程安全问题。3.2 针对原因2原因2与代码结构相关我们可以通过适当的调整代码结构来避免这个问题。但是这还是又局限性的有时候我们的需求就是要同时多个线程同时修改一个变量才能完成。3.3 针对原因3---原子性由于一些线程安全问题的产生是由于该代码执行操作的操作指令不是原子性的。解决方案我们可以通过锁来解决。用锁将涉及到线程安全问题的代码锁起来。如上面由于count导致的线程安全问题也可以用锁来解决。我们针对于线程1和线程2的count操作我们用锁将count的锁起来。如上图线程1和线程2共享一把锁当线程1先进行count时由于锁的原因线程2必须等线程1完成了count的操作之后释放了锁线程2才能执行count操作。在Java中JVM中也提供了一个关键字来帮助我们实现锁-----syncronized关键字---syncronized在面对线程安全问题时我们的主要解决方案就是通过加锁通过加锁我们能够让一个非原子性的操作变成原子性的操作。在Java中JVM中提供了syncronized关键字来实现加锁的操作。1.语法syncronized(锁对象){ //要进行加锁的操作 }关于锁对象Java中的任何类的实例化对象都可以成为锁对象。 锁对象就是我们要用来进行加锁和解锁的锁。进入syncronized代码块就是枷锁退出syncronized代码块就是解锁2.互斥性互斥性是指当有多个线程的操作都对依靠同一个锁对象进行加锁时多个线程之间的syncronized就会产生互斥的效果。也就是说假如线程2执行到syncronized的时候如果线程1没有释放锁那么线程2就会阻塞等待直到线程1将锁释放线程2才会从阻塞队列中退出继续执行线程2的操作。注意当上一个线程释放锁之后下一个线程并不是立即获取到该锁的而是要由操作系统来唤醒下一个线程这也是操作系统调度线程的一部分工作。也就是说假如由123着几个线程线程1先获取到锁接着线程2被操作系统唤醒线程2就尝试获取锁然后接着线程3也被操作系统唤醒了线程3也尝试着获取该锁。但是由于线程1还每释放锁线程2和线程3就会在阻塞队列中进行等待。当线程1释放锁之后尽管线程2比线程3被唤醒的早但是线程2不一定能获取到锁而是和线程3共同竞争该锁。3.解决上述案例的线程安全问题了解了这个我们就可以解决上述案例产生的线程安全问题了。我们直到上述线程安全问题的产生是由于count操作的指令不是原子产生的所以这时候我们可以通过syncronized将count这个操作锁起来让其变成原子性的。public class Demo1 { public static int count0; public static void main(String[] args) throws InterruptedException { Object lockernew Object();//创建锁对象 Thread t1new Thread(()-{ for(int i0;i50000;i){ synchronized (locker){ count; } } }); Thread t2new Thread(()-{ for(int i0;i50000;i){ synchronized (locker){ count; } } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); } }通过syncronized对count操作加锁也就是将loadadd和save这三个指令锁起来了这样当线程t1执行count操作时这三条指令必须全部执行完t1才会释放锁此时t2从才能获取锁进行count操作同样t2在执行count操作时这三条指令全部执行完t2才释放锁。接着t1才能继续获取该锁......,如此循环下去直到线程t1和t2的操作全部执行完毕。因此通过加锁在并发执行中不会出现在线程t1或线程t2在执行count操作时loadaddsave这三条指令没有全部执行完的情况下线程t2或t1来进行count的操作。注意事项我们不是使用了syncronized进行加锁就是成功避免了线程安全问题我们还要考虑要正确得使用锁。比如syncronized的 { } 代码块要合适。更重要的是加锁的锁对象必须是同一个锁对象 因为多个线程针对同一个锁对象加锁才会产生互斥。如果多个线程针对不同对象进行加锁那么多个线程之间是不会产生互斥效果的这时候可以认为syncronized就失效了。4.syncronized的可重入性在Java中多个syncronized代码块对于同一个线程来说是可重入的不会将自己锁死的情况。理解“将自己锁死”简单来所就是一个线程中在syncronized代码块中嵌套了syncronized代码块。也就是一个线程在没有释放锁的情况下又对同一个锁进行加锁操作。按照之前对于锁的设定第二次加锁时由于所没有被释放线程就会阻塞等待直到第一次的锁被释放线程才能获取该锁。但是由于释放第一个锁也是由该线程执行第二次加锁也是由该线程执行这时由于该线程还没有释放锁第二次就无法获取该锁来进行加锁结果就是阻塞在第二次加锁那里就出现了自己将自己锁死的情况。我们将该锁称为“不可重入锁”。但是在Java中syncronized是可重入锁并不会出现上面的问题。如何实现一个可重入锁在可重入所的内部要有“线程持有者”和“计数器”两个信息。1.线程在加锁时我们先判断该锁的线程持有者是不是同一个线程如果恰好是自己持有该锁该线程就不会产生阻塞继续执行下去。2.如果我们遇到syncronized的 { 我们就让计数器自增1解锁的时候遇到 } 就让计数器自减1直到计数器减为0才真正释放该锁。5.syncronized的其他使用方法1.syncronized也可以修饰普通成员方法相当于对this加锁。2.syncronized也可以修饰被static修饰的成员方法由于static的存在就不存在this了就相当于对类对象加锁。3.4 针对原因4---内存可见性可见性是指一个线程对共享变量值的修改能够及时得被其他线程看到。如果一个线程共享变量值的修改无法被其他线程看到也会导致线程安全问题。如以下例子public class Demo4 { public static int flag0; public static void main(String[] args) { Thread t1new Thread(()-{ while (flag0){ } System.out.println(线程t1结束); }); Thread t2new Thread(()-{ Scanner scannernew Scanner(System.in); System.out.println(请输入flag的值); flagscanner.nextInt(); }); t1.start(); t2.start(); } }当我们运行上面代码的时候发现即使我们输入非0的值但是此时t1线程中的循环并没有结束t1线程也在持续运行。我们可以通过jconsole来观察如下图这是什么原因呢 这就涉及到了Java中编译器会自动帮助优化代码的操作 。由于程序员的水平参差不齐所以在Java中编译器就会有一个自动优化代码的操作。编译器会保证代码逻辑不变的情况下自动的帮助程序员优化代码使代码的运行效率更高。编译器虽然声称是优化操作虽然能够保证逻辑不变。但是在某些情况下尤其是在多线程程序中编译器会出现判断失误的情况这就会导致代码在优化前和优化后的逻辑会有些许差异。如上面的代码在上面的代码中线程1有一个循环线程2需要用户输入flag的值。上面代码出现线程安全问题的原因在于while(flag0)这个循环条件的判断操作。虽然flag0看起来只有一行但实际上该操作对应上两条CPU指令。分别为load从内存中读取flag的值cmp将从内存中读到的值存到寄存器中。问题就是出现在第一条指令由于load的指令需要从内存中获取变量的值并且存到寄存器中。此前我们知道从寄存器中读取数据的速度比从内存中读取数据的速度块了几个数量级的倍数并且load的时间开销也是cmp时间的几千倍。并且这又是一个循环判断由于计算机的运行速度非常块且用户不知道什么时候输入flag的新值在这段时间循环就进行很多遍所以此时编译器发现while循环里面的flag0该条件的返回结果总是true所以此时编译器就会将load从内存中读取数据的操作优化成从寄存器中读取数据由于从寄存器中读取数据的效率比从内存中读取数据的效率高所以经过编译器优化后代码的运行效率就提高了。但是这也导致了线程安全问题。当我们在线程2中输入flag的值并将flag的值存到内存中了此时按道理来说由于flag值得改变线程t1中的循环就应该结束了。但是由于编译器将线程t1中从内存中读取数据得操作优化成从寄存器中读取数据这就导致了线程t2修改了flag的值而线程t1无法感知flag值改变这就导致线程t1中得循环持续发生。简单来说线程t1读取的是自己工作内存中的内容t2对flag的值进行改变线程t1无法感知。如下图volatile关键字使用volatile关键字就可以解决上面的问题。只要我们用volatile关键字修饰了flag变量那么就可以强制线程从内存中读取flag从而保证了内存的可见性。public class Demo4 { public volatile static int flag0; public static void main(String[] args) { Thread t1new Thread(()-{ while (flag0){ } System.out.println(线程t1结束); }); Thread t2new Thread(()-{ Scanner scannernew Scanner(System.in); System.out.println(请输入flag的值); flagscanner.nextInt(); }); t1.start(); t2.start(); } }3.5 针对原因5---指令重排序什么是指令重排序一段代码的执行逻辑是下面这样子的1.去前台取优盘2.去教室写10分钟作业3.去前台取快递如果是在单线程情况下JVMCPU指令集会对其进行优化比如按照1-3-2的方式执行也是没问题的少跑一次到前台。这种就叫指令重排序。编译器对于指令重排序优化的前提是“保程逻辑不发生变化”这一点在单线程情况下容易判断。但是在多线程环境下就每那么容易了多线程的代码执行复杂度更高编译器很难在编译阶段对代码的执行效果进行预测因此激进的重排序很容易导致优化之后的逻辑于优化之前不等价。指令重排序的问题也是通过volatile关键字去解决volatile关键子能确保变量的读取和修改操作不会触发重排序。后面在讲解单例模式中会讲解一个指令重排序的案例。

相关新闻

CANN / docs - 配置精度模式
2026/7/5 21:00:53

CANN / docs - 配置精度模式

配置精度模式 【免费下载链接】docs 该仓库用于维护cann公共文档 项目地址: https://gitcode.com/cann/docs 如果在模式转换时不指定网络模型或算子的精度模式,默认采用fp16(float16)数据类型进行计算。 配置模型高精度模式后推理&am…

阅读更多
YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
2026/7/5 21:00:53

YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度

一、本文介绍 本文记录的是利用ASF-YOLO提出的颈部结构优化YOLOv11的目标检测网络模型。将YOLOv11的颈部网络改进成ASF-YOLO的结构,使模型能够有效的融合多尺度特征,捕获小目标精细信息,并根据注意力机制关注小目标相关特征,显著提高模型精度。 专栏目录:YOLOv11改进目录…

阅读更多
Thread类的介绍
2026/7/5 21:00:53

Thread类的介绍

线程是操作系统中的概念,操作系统中的内核实现了线程这种机制,同时,操作系统也提供了一些关于线程的API让程序员来创建和使用线程。在JAVA中,Thread类就可以被视为是对操作系统中提供一些关于线程的API的的进一步的封装。多线程程…

阅读更多
线程安全介绍
2026/7/5 21:00:53

线程安全介绍

前面我们提到了多线程的概念,由于操作系统对线程的调度是随机的,抢占式执行。因此,在多线程程序中就有可能出现了线程安全问题。1.线程安全问题一段代码如果在多线程并发执行的情况下,出现了bug,就称为线程安全问题。反…

阅读更多
CANN / docs - 配置精度模式
2026/7/5 21:00:53

CANN / docs - 配置精度模式

配置精度模式 【免费下载链接】docs 该仓库用于维护cann公共文档 项目地址: https://gitcode.com/cann/docs 如果在模式转换时不指定网络模型或算子的精度模式,默认采用fp16(float16)数据类型进行计算。 配置模型高精度模式后推理&am…

阅读更多
YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
2026/7/5 21:00:53

YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度

一、本文介绍 本文记录的是利用ASF-YOLO提出的颈部结构优化YOLOv11的目标检测网络模型。将YOLOv11的颈部网络改进成ASF-YOLO的结构,使模型能够有效的融合多尺度特征,捕获小目标精细信息,并根据注意力机制关注小目标相关特征,显著提高模型精度。 专栏目录:YOLOv11改进目录…

阅读更多
Thread类的介绍
2026/7/5 21:00:53

Thread类的介绍

线程是操作系统中的概念,操作系统中的内核实现了线程这种机制,同时,操作系统也提供了一些关于线程的API让程序员来创建和使用线程。在JAVA中,Thread类就可以被视为是对操作系统中提供一些关于线程的API的的进一步的封装。多线程程…

阅读更多
Vue-Croppa与TypeScript:如何在TypeScript项目中完美集成
2026/7/5 21:00:53

Vue-Croppa与TypeScript:如何在TypeScript项目中完美集成

Vue-Croppa与TypeScript:如何在TypeScript项目中完美集成 【免费下载链接】vue-croppa A simple straightforward customizable mobile-friendly image cropper for Vue 2.0. 项目地址: https://gitcode.com/gh_mirrors/vu/vue-croppa Vue-Croppa是一个简单、…

阅读更多
SillyTavern 1.18.0 企业级部署指南:构建高可用AI对话系统
2026/7/5 20:00:53

SillyTavern 1.18.0 企业级部署指南:构建高可用AI对话系统

SillyTavern 1.18.0 企业级部署指南:构建高可用AI对话系统 【免费下载链接】SillyTavern LLM Frontend for Power Users. 项目地址: https://gitcode.com/GitHub_Trending/si/SillyTavern SillyTavern是一款专为高级用户设计的LLM前端界面,提供强…

阅读更多
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御
2026/7/5 0:00:50

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

阅读更多
3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略
2026/7/5 0:00:50

3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略

3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾为Windows右键菜单中那些…

阅读更多
GXDE OS下Wayland兼容性实战:从deepin-mutter原理到VMware Tools修复
2026/7/5 0:00:50

GXDE OS下Wayland兼容性实战:从deepin-mutter原理到VMware Tools修复

如果你正在用 GXDE OS 或者任何基于 Deepin 的发行版,并且遇到了“检测到窗口系统采用 Wayland 协议,程序即将退出”这类弹窗,或者发现 VMware Tools 在 Ubuntu 24.04 这类默认 Wayland 的系统上启动失败,那这篇文章就是为你准备的…

阅读更多
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御
2026/7/5 0:00:50

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

阅读更多
3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略
2026/7/5 0:00:50

3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略

3步彻底解决Windows右键菜单混乱问题:ContextMenuManager使用全攻略 【免费下载链接】ContextMenuManager 🖱️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager 你是否曾为Windows右键菜单中那些…

阅读更多
GXDE OS下Wayland兼容性实战:从deepin-mutter原理到VMware Tools修复
2026/7/5 0:00:50

GXDE OS下Wayland兼容性实战:从deepin-mutter原理到VMware Tools修复

如果你正在用 GXDE OS 或者任何基于 Deepin 的发行版,并且遇到了“检测到窗口系统采用 Wayland 协议,程序即将退出”这类弹窗,或者发现 VMware Tools 在 Ubuntu 24.04 这类默认 Wayland 的系统上启动失败,那这篇文章就是为你准备的…

阅读更多
基于Dify与DeepSeek构建私有知识库问答系统实战指南
2026/7/4 11:17:16

基于Dify与DeepSeek构建私有知识库问答系统实战指南

在业务中快速构建一个能理解私有文档、准确回答专业问题的智能助手,是很多开发团队面临的共同挑战。传统方案往往需要从零开始搭建复杂的 RAG(检索增强生成)系统,涉及文档解析、向量化、检索、大模型调用等多个环节,整…

阅读更多
FAE放射组学分析工具:医学影像特征探索的完整解决方案
2026/7/4 5:24:16

FAE放射组学分析工具:医学影像特征探索的完整解决方案

FAE放射组学分析工具:医学影像特征探索的完整解决方案 【免费下载链接】FAE FeAture Explorer 项目地址: https://gitcode.com/gh_mirrors/fae/FAE 你是否曾经面对海量医学影像数据感到无从下手?想要从CT、MRI等影像中提取有价值的定量特征&#…

阅读更多
DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖!
2026/7/5 15:33:35

DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖!

DesktopNaotu:你的终极离线思维导图解决方案,告别网络依赖! 【免费下载链接】DesktopNaotu 桌面版脑图 (百度脑图离线版,思维导图) 跨平台支持 Windows/Linux/Mac OS. (A cross-platform multilingual Mind Map Tool) 项目地址:…

阅读更多