news 2026/4/7 9:24:52

多线程下的脏读:为什么你的数据“看起来没问题”,结果却全是坑?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多线程下的脏读:为什么你的数据“看起来没问题”,结果却全是坑?

网罗开发(小红书、快手、视频号同名)

大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括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++不是原子操作

它实际分成了三步:

  1. 从内存读取 count
  2. +1
  3. 写回内存

多个线程同时执行时,中间状态是可见的

可见性问题

即便你只读不写,如果:

  • 一个线程在修改
  • 一个线程在读
  • 没有任何同步手段

那么读线程看到的值,可能是旧值、半更新值,甚至完全不可预期

脏读在真实项目里长什么样?

在实际业务中,它不会像 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 更重要。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/6 1:36:50

STM32智能手环:心率血氧体温监测系统设计与实现

STM32智能手环&#xff1a;心率血氧体温监测系统设计与实现摘要本文设计并实现了一种基于STM32F103C8T6微控制器的多功能智能手环系统&#xff0c;用于个人健康实时监测与运动数据管理。系统集成了MAX30102光学传感器、DS18B20温度传感器和ADXL345三轴加速度计&#xff0c;实现…

作者头像 李华
网站建设 2026/3/29 4:40:36

微信小程序uniapp-vue英语学习小助手的设计

文章目录具体实现截图主要技术与实现手段系统设计与实现的思路系统设计方法java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;带文档1万…

作者头像 李华
网站建设 2026/4/1 11:05:07

雷家林诗歌集录之十二Collection of Poems by Lei Jialin, Volume 12

SunsetIn the glow of the setting sun, a boat’s silhouette lies. A distant mountain range catches the sea - wind’s sigh. With the fish and dragons asleep, homesickness takes hold. Like a silk ribbon, the rosy clouds stir deep longing untold.Four - Charact…

作者头像 李华
网站建设 2026/4/3 4:13:02

「AI元人文构想」对话全记录:从困境、构想到系统自洽的七十日

「AI元人文构想」对话全记录&#xff1a;从困境、构想到系统自洽的七十日一、 缘起&#xff1a;穿透表象的野心与链接的失效 初始接触&#xff1a;用户首先分享了一篇题为《穿透表象&#xff1a;在“人类在环规则在场语境主权”框架下重审AI元人文构想的风险与未来》的论文摘要…

作者头像 李华
网站建设 2026/4/2 14:48:17

java基础-IO流-2(了解)

IO流中捕获异常package Day07_IO;import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException;public class IOTest07 {public static void main(String[] args) throws IOException {//利用try cat…

作者头像 李华