Skip to content

在并发量比较大的情况下,CAS 会不会出现两个线程同时比较且都成功更新的情况?乐观锁在这种情况下能保证并发安全吗?

约 1143 字大约 4 分钟

多线程与并发美团

2025-03-26

⭐ 题目日期:

美团 - 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 同时尝试更新同一变量:

  1. 初始状态

    • 内存值 V = 100,版本号 Version = 1。
  2. 操作时序

    • T1 和 T2 同时读取到 V = 100,Version = 1。
    • T1 先执行 CAS:检查 Version 是否为 1 → 是,更新 V = 200,Version = 2。
    • T2 后执行 CAS:检查 Version 是否为 1 → 否(此时 Version 已变为 2),更新失败。
  3. 结果

    • 仅 T1 成功,T2 需重试或放弃。

5. 乐观锁的适用性与限制

  • 适用场景

    • 读多写少(冲突概率低)。
    • 允许重试(如自旋、退避重试或业务补偿)。
  • 潜在问题

    • 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 和乐观锁能够保证并发安全,不会出现多个线程同时更新成功的情况。其核心在于原子性操作和冲突检测机制,失败线程通过重试逻辑最终完成更新。