线程安全、线程同步、竞态条件

一、线程安全(Thread Safety)

定义:

当多个线程同时访问一个对象/数据时,不管操作系统怎么调度这些线程,也不管它们的执行顺序怎么穿插交织,最终的结果都必须是正确的,不会出现数据错乱、重复、丢失等问题。

核心就一句话:并发执行,结果仍对。

类比: 就像银行转账。你同时用手机APP和网页端各转一笔钱,银行系统必须保证:

  • 不会因为你两个渠道同时操作,就把你的钱算错

  • 不会转出去双倍的钱

  • 也不会吞掉你的钱

你的系统现在线程不安全,因为两个用户同时审核,生成了两张入库单------结果错了。


二、线程同步(Thread Synchronization)

定义:

协调多个线程对共享资源的访问顺序,让它们"有规矩"地执行,避免互相干扰、互相覆盖。

给共享资源加把锁,保证同一时刻只有一个线程可以访问他。

核心就一句话:给并发访问定规矩。

类比: 想象一个单向通行的隧道:

  • 没有同步:两辆车对头开,直接在隧道中间撞车(数据冲突)

  • 有同步:要么装红绿灯轮流走(互斥),要么分道行驶各走各的(隔离)

同步只是实现"线程安全"的一种手段,不是唯一手段。除了"排队访问"(同步),还可以通过"各用各的"(ThreadLocal)或"只读不写"(不可变对象)来实现安全。


三、竞态条件(Race Condition)

定义:

多个线程同时读写共享资源,最终的结果取决于它们执行的"先后顺序"和"时机"------就像赛跑,谁先谁后到终点,结果完全不同。

核心就一句话:结果看运气,取决于谁先执行完。


"检查-执行"模式:竞态条件的温床

在业务代码里,最常见的就是 "检查-然后-执行"(Check-Then-Act) 模式:

复制代码
// 第1步:检查条件
if (单据状态.equals("待审核")) {
    // 第2步:执行动作
    updateStatus("已审核");
    createStockInOrder();
}

单线程下,这段代码毫无问题。但多线程下,"检查"和"执行"是两个独立的操作,中间存在时间窗口。另一个线程完全可以趁这个空档插入进来,基于同样过期的检查结果也执行一遍。


你的审核Bug时间线

复制代码
时间点        用户A的线程                    用户B的线程                 数据库状态
─────────────────────────────────────────────────────────────────────────────────────
09:00:00.100  【检查】查询状态 → 待审核                                  待审核
09:00:00.150                                【检查】查询状态 → 待审核     待审核
              ↑                                                        ↑
              └────────── 两人都通过了"检查",都准备执行 ──────────────┘

09:00:00.200  【执行】更新为已审核,生成入库单①                          已审核
09:00:00.250                                【执行】更新为已审核,
                                            生成入库单②                 已审核
                                            ↑
                                            └─ B也执行了!因为B检查时状态还是"待审核"

同样的代码,同样的操作,只是因为线程执行的时机不同:

执行顺序 结果
A 完整做完(检查+执行)→ B 再做 ✅ 只生成一张入库单
A 检查完 → B 检查完 → A 执行 → B 执行 ❌ 生成两张入库单

一句话总结

竞态条件 = "检查-执行"模式在多线程下被并发打断,另一个线程基于过期的检查结果执行了动作,导致程序正确性取决于不可控的线程调度顺序。


四、三者之间的关系

复制代码
┌─────────────────────────────────────────────┐
│                                             │
│   竞态条件(Race Condition)                 │
│   ↓ 是问题的根源                             │
│   "多线程读写共享资源,时机不确定导致结果错乱"    │
│                                             │
│            ↓ 需要用手段解决                    │
│                                             │
│   线程同步(Thread Synchronization)         │
│   ↓ 是解决问题的手段                          │
│   "协调线程访问顺序,给并发定规矩"              │
│                                             │
│            ↓ 最终实现目标                      │
│                                             │
│   线程安全(Thread Safety)                  │
│   ↓ 是最终要达到的状态                        │
│   "不管怎么并发,结果始终正确"                 │
│                                             │
└─────────────────────────────────────────────┘

五、结合你的审核Bug串起来

概念 在你的系统里具体表现
竞态条件 用户A和用户B几乎同时查询单据状态,都查到"待审核",然后各自执行了审核逻辑
线程同步缺失 代码里"查询判断"和"更新状态"之间没有加锁或版本控制,两个线程穿插执行没人管
线程不安全 最终结果错误:同一张单据生成了两张入库单,数据库里状态虽然都是"已审核",但业务数据重复了

六、一句话记住三者

竞态条件 = "为什么会出事"(多线程抢资源,时机不可控)
线程同步 = "怎么防止出事"(给并发定规矩,排队或事后检查)
线程安全 = "出事了吗"(最终结果对不对,是目标也是检验标准)

你的Bug修复,本质上就是用线程同步的手段(悲观锁/乐观锁),消除竞态条件,最终实现线程安全

相关推荐
xieliyu.2 小时前
计算机网络|数据链路层详解:MAC 地址、交换机、ARP 协议、CRC 校验
java·网络·笔记·计算机网络·java-ee
SimonKing2 小时前
手机投屏到电脑,不用装任何 App,这个开源工具免费搞定:QtScrcpy
java·后端·程序员
学长毕业设计3 小时前
基于SpringBoot的奶茶店服务管理系统的设计与实现(源码+文档+讲解视频)
java·spring boot·后端
蛋先生DX3 小时前
明明都是源码到CPU,各语言中间原理咋不是一个套路?
java·javascript·go
萧瑟余晖3 小时前
Java深入解析篇十三之Java日志API(JUL)详解
java·开发语言
随遇而安zx3 小时前
Spring Cloud Gateway 微服务网关 设计思想与深度解析
java·spring cloud·微服务·架构
catino3 小时前
spring-事务@Transactional
java·数据库·spring
luj_17683 小时前
毒素驱动的生物自毁机制探析
服务器·c语言·开发语言·经验分享·算法
万年咸鱼3 小时前
Java List 接口详解:从基础到实战
java·windows·list
海兰3 小时前
【 Python 量化交易】第6章:数据可视化
开发语言·python·信息可视化