大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- 什么是多线程下的脏读?
- 一个最常见、也最危险的场景
- 场景设定
- 问题代码:共享变量无锁访问
- Demo 1:典型脏读示例
- 你可能会想
- 为什么这段代码会出问题?
- `count++` 不是原子操作
- 可见性问题
- 脏读在真实项目里长什么样?
- 解决方案一:synchronized(最直接、最稳)
- 什么时候该用?
- Demo 2:使用 synchronized 解决脏读
- synchronized 到底解决了什么?
- 代价是什么?
- 解决方案二:AtomicInteger(高并发下的常用方案)
- Demo 3:使用 AtomicInteger
- AtomicInteger 的核心优势
- 它的局限
- 解决方案三:LongAdder(超高并发统计神器)
- Demo 4:使用 LongAdder
- LongAdder 为什么更快?
- 注意点
- 实际项目中该怎么选?
- 一个真实业务例子
- 例子:接口 QPS 统计
- 总结
前言
在日常开发里,只要项目稍微上点规模,多线程几乎是绕不开的。
线程池、异步任务、并发计数、实时统计……
而脏读,往往就是这些场景里最容易被忽略、却最容易埋雷的问题之一。
这篇文章我们不讲教科书式定义,直接从真实问题出发,一步一步拆解:
- 什么是多线程下的脏读
- 为什么你“只是读个变量”,也会出问题
- synchronized、AtomicInteger、LongAdder 分别解决了什么问题
- 实际项目中该怎么选
什么是多线程下的脏读?
先说一句大白话版本:
脏读 = 一个线程读到了另一个线程“还没写完 / 不稳定”的数据
在单线程世界里,这种事根本不可能发生。
但在多线程环境下,只要你共享变量 + 没有同步手段,脏读就是迟早的事。
一个最常见、也最危险的场景
场景设定
假设你在做一个统计功能:
- 多个线程不断更新一个计数器
- 另外一个线程负责读取当前统计值并展示
你可能会写出下面这样的代码。
问题代码:共享变量无锁访问
Demo 1:典型脏读示例
publicclassDirtyReadDemo{staticintcount=0;publicstaticvoidmain(String[]args)throwsInterruptedException{Threadwriter=newThread(()->{for(inti=0;i<100_000;i++){count++;}});Threadreader=newThread(()->{for(inti=0;i<100_000;i++){if(count<0){System.out.println("读到了异常值:"+count);}}});writer.start();reader.start();writer.join();reader.join();System.out.println("最终 count = "+count);}}你可能会想
不就是一个
int自增吗?
Java 里 int 读写不是原子的吗?
这里正好是第一个坑。
为什么这段代码会出问题?
count++不是原子操作
它实际分成了三步:
- 从内存读取 count
- +1
- 写回内存
多个线程同时执行时,中间状态是可见的。
可见性问题
即便你只读不写,如果:
- 一个线程在修改
- 一个线程在读
- 没有任何同步手段
那么读线程看到的值,可能是旧值、半更新值,甚至完全不可预期。
脏读在真实项目里长什么样?
在实际业务中,它不会像 Demo 那么“明显”,而是表现为:
- 偶发统计错误
- 日志里出现不可能的状态
- QA 环境永远复现不了,线上偶发
这也是为什么脏读特别难排查。
解决方案一:synchronized(最直接、最稳)
什么时候该用?
- 并发量不大
- 逻辑复杂
- 你希望绝对正确
Demo 2:使用 synchronized 解决脏读
publicclassSynchronizedDemo{staticintcount=0;staticfinalObjectlock=newObject();publicstaticvoidmain(String[]args)throwsInterruptedException{Threadwriter=newThread(()->{for(inti=0;i<100_000;i++){synchronized(lock){count++;}}});Threadreader=newThread(()->{for(inti=0;i<100_000;i++){synchronized(lock){intvalue=count;// 这里读取到的一定是一个“完整、一致”的值}}});writer.start();reader.start();writer.join();reader.join();System.out.println("最终 count = "+count);}}synchronized 到底解决了什么?
- 保证同一时间只有一个线程访问共享变量
- 保证原子性 + 可见性
代价是什么?
- 性能开销
- 高并发下容易成为瓶颈
解决方案二:AtomicInteger(高并发下的常用方案)
如果你的需求只是:
- 自增
- 计数
- 状态标记
那 AtomicInteger 基本是第一选择。
Demo 3:使用 AtomicInteger
importjava.util.concurrent.atomic.AtomicInteger;publicclassAtomicDemo{staticAtomicIntegercount=newAtomicInteger(0);publicstaticvoidmain(String[]args)throwsInterruptedException{Threadwriter=newThread(()->{for(inti=0;i<100_000;i++){count.incrementAndGet();}});Threadreader=newThread(()->{for(inti=0;i<100_000;i++){intvalue=count.get();// 永远不会读到脏数据}});writer.start();reader.start();writer.join();reader.join();System.out.println("最终 count = "+count.get());}}AtomicInteger 的核心优势
- 基于 CAS(Compare-And-Swap)
- 无锁
- 性能远好于 synchronized
它的局限
- 只适合简单数值操作
- 多个变量组合逻辑不适合
解决方案三:LongAdder(超高并发统计神器)
当你面对的是:
- 高并发写
- 偶尔读
- 对实时精确度要求不极端
那LongAdder会非常香。
Demo 4:使用 LongAdder
importjava.util.concurrent.atomic.LongAdder;publicclassLongAdderDemo{staticLongAddercounter=newLongAdder();publicstaticvoidmain(String[]args)throwsInterruptedException{Threadwriter=newThread(()->{for(inti=0;i<100_000;i++){counter.increment();}});Threadreader=newThread(()->{for(inti=0;i<100_000;i++){longvalue=counter.sum();}});writer.start();reader.start();writer.join();reader.join();System.out.println("最终 count = "+counter.sum());}}LongAdder 为什么更快?
- 内部拆分成多个 Cell
- 不同线程更新不同 Cell
- 读的时候再汇总
注意点
sum()不是强一致的瞬时值- 适合统计类场景,不适合金融级精度
实际项目中该怎么选?
我给你一个非常工程化的选择建议:
| 场景 | 推荐方案 |
|---|---|
| 逻辑复杂,强一致 | synchronized |
| 简单计数,高并发 | AtomicInteger |
| 超高并发统计 | LongAdder |
| 多变量事务 | synchronized / Lock |
一个真实业务例子
例子:接口 QPS 统计
- 每个请求进来都 +1
- 定时上报监控系统
- 不要求每一毫秒都精准
LongAdder 是最合适的选择
总结
多线程下的脏读,不是“写错代码”,而是“没意识到并发模型”。
- 没锁 ≠ 性能高
- 原子类 ≠ 万能
- LongAdder ≠ 精准计数
理解每种工具解决了什么问题、牺牲了什么,比死记 API 更重要。