外观
在并发量比较大的情况下,CAS 会不会出现两个线程同时比较且都成功更新的情况?乐观锁在这种情况下能保证并发安全吗?
⭐ 题目日期:
美团 - 2024/12/23
📝 题解:
在高并发环境下,CAS(比较并交换)操作和乐观锁机制能够有效保证并发安全,以下是详细的解释:
1. CAS 操作的原子性保证
CAS 原理:
CAS 是一种原子操作,包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。只有当 V 的值等于 A 时,才会将 V 的值更新为 B。整个过程由硬件(如 CPU 指令)保证原子性,确保同一时刻只有一个线程能成功更新。高并发场景:
即使多个线程同时读取到相同的旧值(A),并尝试执行 CAS:- 仅一个线程成功:硬件级别的原子性保证只有一个线程的 CAS 操作会成功。
- 其他线程失败:剩余线程的 CAS 操作会失败,需通过重试或业务逻辑处理冲突。
2. 乐观锁的并发安全机制
乐观锁实现:
乐观锁通过**版本号(Version)或时间戳(Timestamp)**实现,每次更新数据时检查版本号是否匹配。例如:UPDATE table SET value = new_value, version = version + 1 WHERE id = 1 AND version = current_version;
- 原子性校验:数据库或代码逻辑确保版本号的检查和更新是原子的。
高并发冲突处理:
- 场景:两个线程同时读取到相同版本号(current_version),并尝试提交更新。
- 结果:
- 第一个线程的更新操作成功,版本号递增。
- 第二个线程的更新操作失败(
WHERE version = current_version
不再成立),需通过重试机制重新读取数据并再次提交。
3. 为何不会出现多个线程同时更新成功?
CAS 的硬件原子性:
硬件(如 CPU 的cmpxchg
指令)确保同一内存位置的 CAS 操作是原子的,物理上不可能同时成功。数据库事务的隔离性:
在数据库场景中,乐观锁的更新语句(如UPDATE ... WHERE version = ?
)在事务隔离级别(如REPEATABLE READ
)下,会通过行锁或 MVCC 机制保证原子性。
4. 示例:高并发下的 CAS 流程
假设线程 T1 和 T2 同时尝试更新同一变量:
初始状态:
- 内存值 V = 100,版本号 Version = 1。
操作时序:
- T1 和 T2 同时读取到 V = 100,Version = 1。
- T1 先执行 CAS:检查 Version 是否为 1 → 是,更新 V = 200,Version = 2。
- T2 后执行 CAS:检查 Version 是否为 1 → 否(此时 Version 已变为 2),更新失败。
结果:
- 仅 T1 成功,T2 需重试或放弃。
5. 乐观锁的适用性与限制
适用场景:
- 读多写少(冲突概率低)。
- 允许重试(如自旋、退避重试或业务补偿)。
潜在问题:
- ABA 问题:若变量从 A → B → A,CAS 无法感知中间变化。
解决方案:使用递增版本号代替直接值比较。 - 活锁风险:高频冲突时重试可能导致线程饥饿。
解决方案:限制重试次数或引入随机退避。
- ABA 问题:若变量从 A → B → A,CAS 无法感知中间变化。
6. 代码示例:CAS 与乐观锁实现
Java 中的 CAS(AtomicInteger):
AtomicInteger value = new AtomicInteger(100);
// 线程 T1
int expected = value.get();
if (value.compareAndSet(expected, 200)) {
System.out.println("T1 更新成功");
}
// 线程 T2
int expected = value.get();
if (value.compareAndSet(expected, 300)) {
System.out.println("T2 更新成功"); // 必然失败(因 T1 已更新)
}
数据库乐观锁(伪代码):
-- 初始查询
SELECT value, version FROM table WHERE id = 1;
-- 更新逻辑(需在事务中)
BEGIN;
UPDATE table SET value = new_value, version = version + 1
WHERE id = 1 AND version = current_version;
COMMIT;
-- 若受影响行数为 0,表示冲突,需重试。
7. 总结
关键点 | 说明 |
---|---|
CAS 的原子性 | 硬件保证同一时刻仅一个线程成功,其他线程失败。 |
乐观锁的安全性 | 通过版本号原子校验 + 失败重试机制,确保最终一致性。 |
高并发场景下的行为 | 线程冲突时,失败方需重试或降级处理,但数据不会损坏。 |
注意事项 | 处理 ABA 问题、限制重试次数以避免活锁。 |
结论:
在高并发场景下,CAS 和乐观锁能够保证并发安全,不会出现多个线程同时更新成功的情况。其核心在于原子性操作和冲突检测机制,失败线程通过重试逻辑最终完成更新。