Java多线程:线程不安全产生的原因及解决方法
在 Java 并发编程中线程安全是绕不开的核心问题。当多个线程同时执行代码运行结果和单线程预期不一致就发生线程不安全。本文结合底层原理梳理线程不安全的根源以及对应的解决手段synchronized, volatile, wait, notify, 设计模式等。1.什么是线程不安全如果多线程环境下程序运行结果, 和单线程环境下预期结果不一致, 就代表线程不安全经典示例两个线程同时对同一个变量做 50000 次自增预期最终结果 100000实际运行结果往往小于 100000。我们用代码举例试试看:private static int count0; public static void main(String[] args) throws InterruptedException{ Thread t1new Thread(()-{ for (int i 0; i 50000; i) { count; } }); Thread t2new Thread(()-{ for (int i 0; i 50000; i) { count; } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count: count); }运行结果:很明显的看到两次运行的结果不同, 且都小于10000注: count 看上去是一行java代码,底层是分为三个步骤:从主内存读取到数据到CPU寄存器在寄存器完成1 运行将计算结果写回主内存线程抢占调度会打断这三步多个线程同时读写同一个变量就出现数据错乱。2.线程不安全的四大根本原因2.1 线程抢占式调度(根本前提)操作系统对线程是抢占式调度线程什么时候让出 CPU、什么时候被调度执行是随机、不可预测的。 多个线程执行顺序千变万化如果代码没有做并发保护在某些调度时序下就会出错。2.2 操作不具有原子性原子性: 一段操作, 要么全部执行完毕, 要么完全不执行, 中间不能被其他线程打断很多 Java 语句不是原子操作例如 count 是 3 条 CPU 指令。线程执行一半被切走其他线程介入修改数据就产生错误。类比卖票系统线程 A 读到还有 1 张票还没完成票数更新线程 B 同时读到还有 1 张票两个线程都完成卖票同一张票卖给两个人。2.3 内存可见性问题2.3.1 java内存模型JMM:主内存: 存放共享变量(对应物理内存)工作内存: 每个线程独有, 本质是CPU寄存器 高速缓存2.3.2 线程修改共享变量流程:将主内存变量拷贝副本到自己的工作内存在工作内存修改副本再把副本刷新回主内存可见性问题: 一个线程修改了工作内存的副本, 没有及时刷新到主内存, 其他线程读取到主内存拿到的值还是旧值, 感知不到修改.比如子线程循环读取 flag主线程修改 flag子线程永远看不到 flag 的变化循环无法退出。2.4指令重排序为提升执行效率编译器、CPU 会对指令做重排序优化在保证单线程逻辑不变前提下调整代码执行顺序。 单线程下重排序没有问题, 但多线程环境重排序会打乱多线程之间依赖逻辑引发 bug。例如单例模式instance new Singleton()底层分为分配内存、初始化对象、赋值引用三步重排序可能出现先赋值引用再初始化对象其他线程拿到半初始化的对象。总结: 触发线程安全的必要条件多线程 共享变量 对变量进行修改。只读共享变量不会线程不安全。3.线程不安全的解决方案解决线程安全就是解决上面的四个问题:保证原子性, 保证可见性, 抑制不合理重排序, 同时规避抢占调度带来的执行顺序混乱.3.1 方案一: synchronized 同步锁(监视器锁 MonitorLock)synchronized 可以同时保证: 原子性, 可见性, 抑制指令重排序核心是互斥: 同一时刻, 只有一个线程能拿到锁执行同步代码块, 其他线程阻塞等待锁释放synchronized 三种用法1. 修饰代码块, 指定锁对象Object lockernew Object(); synchronized (locker){ count; }2.修饰普通方法: 锁当前对象 thispublic synchronized void add(){ count; }3. 修饰静态方法: 锁类对象 (类的Class对象)public static synchronized void add(){ count; }特性:互斥:竞争同一把锁才会阻塞, 不同锁之间互不影响可重入: 同一个线程可以重复获取同一把锁, 不会自己把自己锁死, 内部维护线程持有者计数器修改上面自增不安全的代码, 加上synchronized 就可以得到正确的10000结果:private static int count0; private static Object lockernew Object(); public static void main(String[] args) throws InterruptedException{ Thread t1new Thread(()-{ for (int i 0; i 50000; i) { synchronized (locker) { count; } } }); Thread t2new Thread(()-{ for (int i 0; i 50000; i) { synchronized (locker) { count; } } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count: count); }运行结果:3.2 方案二: volatile关键字volatile两大能力保证内存可见性读写 volatile 变量强制和主内存交互不使用线程本地缓存副本写操作立刻刷新到主内存读操作每次从主内存读取最新值。禁止指令重排序。⚠️ 重点volatile 不能保证原子性适合场景一个线程写多个线程读解决可见性。不适合场景多线程同时写同一个变量比如 count加 volatile 依旧线程不安全。例: 解决标志位可见性问题static class Counter{ public volatile int flag0; }面试高频: 双重校验锁DCL懒汉单例, instance 必须加volatile, 防止指令重排序导致拿到半初始化对象.class Singleton{ //静态共享变量,volatile是关键 private static volatile Singleton instancenull; //私有构造方法: 防止外部new对象 private Singleton(){} //对外获取实例的静态方法 public static Singleton getInstance(){ //第一层判断: 外层if,减少抢锁开销 if (instancenull){ //类锁,多个线程竞争这一把锁 synchronized (Singleton.class){ //第二次判断: 内层if,防止多线程等待锁之后重复创建对象 if (instancenull){ instancenew Singleton(); } } } return instance; } }3.3 方案三: wait/notify 做线程之间的协调synchronized解决竞wait() / notify() / notifyAll()解决线程之间的执行顺序协调。wait()释放锁线程进入等待队列等待被唤醒必须写在 synchronized 同步块内。notify()唤醒该对象等待队列中随机一个等待线程不会立刻释放锁退出同步块才释放锁。notifyAll()唤醒该对象等待队列全部线程唤醒后全部线程重新竞争锁。注: wait 建议放在while 循环判断条件, 防止虚假唤醒. 典型使用场景: 阻塞队列, 生产者-消费者类型.3.4 方案四: 规避共享可变数据(无锁思路)不共享数据每个线程使用局部变量栈上变量线程私有不存在多线程竞争。共享只读数据变量只读取不修改没有写操作不会线程不安全例如String不可变类。3.5 方案五: 使用线程安全的类Java 标准库区分线程安全 / 不安全集合❌线程不安全ArrayList、HashMap、HashSet、StringBuilder✅线程安全StringBuffer、Vector、HashTable不推荐性能差ConcurrentHashMap并发推荐BlockingQueue阻塞队列天生线程安全生产者消费者模型首选。3.6 方案六: 线程池复用线程减少频繁创建销毁线程开销同时管理多线程任务配合阻塞队列做任务管理。4.面试高频对比4.1 synchronized vs volatilesynchronizedvolatile原子性✅保证❌不保证可见性✅保证✅保证重排序✅禁止✅禁止作用修饰代码块 / 方法加锁互斥修饰变量使用场景多线程读写共享变量一个线程写、多线程读解决可见性4.2 wait() vs sleep()wait()是 Object 方法sleep()是 Thread 静态方法。wait()必须在synchronized中调用会释放锁sleep()不需要锁不会释放锁。wait()用于线程间通信等待其他线程 notify 唤醒sleep()单纯休眠一段时间时间到自动恢复。5.总结优先避免问题尽量不使用共享可变对象使用局部变量使用不可变对象只读。保证原子性多线程修改共享变量使用synchronized锁保证操作不可被打断。保证可见性变量用volatile修饰或者使用锁synchronized 自带可见性。禁止指令重排序volatile、synchronized都可以限制重排序。协调线程执行顺序wait‑notify、阻塞队列做生产者消费者模型。注意锁会带来性能损耗不要无脑到处加锁只把真正存在竞争的临界区代码放进同步块缩小锁粒度。

相关新闻

傅里叶分析与自适应信号分解的MATLAB实现

傅里叶分析与自适应信号分解的MATLAB实现

1. 项目背景与核心价值在信号处理领域,如何有效地分解复杂信号一直是工程师们面临的经典难题。传统傅里叶变换虽然能提供全局频域信息,但对于非平稳信号的处理往往力不从心。我在最近的一个工业设备故障诊断项目中,就遇到了振动信号分析的需求…

2026/10/14 14:25:38 阅读更多 →
老单机版CAD在新系统上的安装排错与授权配置完整指南

老单机版CAD在新系统上的安装排错与授权配置完整指南

简介:这是面向服装打板与纸样设计场景的单机版CAD工具包,软件品牌为“丝绸之路”(SILKROAD),S4型。资源专为服装设计初学者、制版师及服装专业学生准备,用于在电脑上完成精确制图、版型调整和放码&#xff…

2026/10/14 14:24:37 阅读更多 →
深入解析actix-web高性能请求处理管线

深入解析actix-web高性能请求处理管线

1. 项目概述Actix-web作为Rust生态中最受欢迎的高性能Web框架之一,其请求处理管线(Request Processing Pipeline)的设计直接决定了框架的吞吐量和延迟表现。本文将深入剖析actix-web从接收请求到返回响应的完整生命周期,揭示其实现…

2026/10/14 14:24:37 阅读更多 →

最新新闻

机器视觉项目成败的关键:工业光学系统设计与打光实战解析

机器视觉项目成败的关键:工业光学系统设计与打光实战解析

1. 先搞清楚一件事:机器视觉项目为什么栽在“光”上先说个我经常在行业交流里遇到的尴尬场面:客户拿着一个检测需求找过来,软件工程师看完说“算法不难”,电气工程师说“PLC程序简单”,结果项目一启动,所有…

2026/10/14 15:04:40 阅读更多 →
工业AI落地实战:从场景选择到工程化避坑指南

工业AI落地实战:从场景选择到工程化避坑指南

1. 一场直播引发的思考:工业AI到底卡在哪前几天蹲了一场关于工业AI的直播回顾,主题很直接——“工业AI的下一个机会在哪”。说实话,这类话题这两年听得耳朵都快起茧子了,但这场直播里几位一线从业者聊的东西,倒是让我觉…

2026/10/14 15:04:40 阅读更多 →
Nginx主机头变量解析:$http_host、$host与$proxy_host区别与应用

Nginx主机头变量解析:$http_host、$host与$proxy_host区别与应用

1. 理解Nginx中的主机头变量在Nginx配置和日志分析过程中,我们经常会遇到$http_host、$host和$proxy_host这三个与主机头相关的变量。很多开发者在实际使用时容易混淆它们的区别,导致配置错误或日志记录不准确。作为使用Nginx多年的运维工程师&#xff0…

2026/10/14 15:04:40 阅读更多 →
SUSE 12离线环境Docker部署与私有Registry搭建实践指南

SUSE 12离线环境Docker部署与私有Registry搭建实践指南

先交代一个背景:我在某项目的内网环境里折腾过好几轮容器基础设施,其中最关键的一步,就是在没有任何外网连接的 SUSE 12 服务器上把 Docker 跑起来,再搭一个能用的私有镜像仓库。这个事听起来简单,但真正操作过的人都知…

2026/10/14 15:04:40 阅读更多 →
局域网自托管项目管理软件选型:5款主流系统横向评测

局域网自托管项目管理软件选型:5款主流系统横向评测

前阵子帮一家制造业公司做内部办公工具选型,对方技术负责人上来就明确了一句话:项目数据、代码文档、人员账号,全都不能出公司内网。这一句话直接让市面上一大半好用又成熟的SaaS项目管理产品出局,剩下能选的路基本只剩一条——在…

2026/10/14 15:04:40 阅读更多 →
JSON解析库完全指南:从原理到实战的避坑手册

JSON解析库完全指南:从原理到实战的避坑手册

上周我在排查一个线上数据同步任务时,发现整个链路慢了三倍,最后定位到根因竟是日志中夹杂的一个超大JSON数组被反复加载进内存。这个项目本身已经上线一年多,代码里用的也是大家眼中"最标准"的解析方式,但恰恰是这种标…

2026/10/14 15:03:37 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →