一、线程安全(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修复,本质上就是用线程同步的手段(悲观锁/乐观锁),消除竞态条件,最终实现线程安全。