专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

乐观锁详解:CAS机制、Atomic原子类与版本号机制的原理与应用

今天继续更新并发锁机制系列,前两篇文章更新了悲观锁中的SynchronizedReentrantLock,相比于悲观锁,乐观锁认为并发冲突是小概率事件。在访问共享资源时不会加锁,而是通过一定机制(如CAS机制、版本号机制)来检测是否存在其他线程的干扰。

本文将从CAS机制与自旋、Atomic原子类、版本号机制三部分来探究乐观锁。

  • 1.CAS机制与自旋
  • 1.1 CAS机制
  • 1.2 自旋
  • 2.Atomic原子类
  • 3.版本号机制
  • 3.1 为什么要引入版本号机制

3.2 版本号机制执行流程

1.CAS机制与自旋

1.1 CAS机制

CAS(Compare And Swap,比较并交换) 是一种由 CPU 原生指令支持的原子操作,常用于实现并发环境下的非阻塞算法(Lock-Free Algorithm)。在 Java 中,CAS 被广泛用于实现乐观锁机制,它的最大特点是无需加锁,就可以确保变量的并发安全更新。

CAS 操作涉及三个核心参数:

参数 含义
V(Value) 变量当前在主内存中的真实值
E(Expected) 操作者认为变量应该具有的期望值
N(New) 若变量值等于期望值时,想要写入的新值

这三个值共同决定了 CAS 操作的行为。

可以将 CAS 的核心逻辑想象成一个函数,伪代码如下:

boolean CAS(address,expectValue,swapValue) {
 if(&address == expectedValue) {
  &address = swapValue;
  return true;
 }
 return false;
}

(1)进入CAS操作前:线程 T1 尝试更新一个共享变量 x,首先会读取该变量在主内存中的值,这个值被作为当前线程的“预期值(Expected,E)”。

(2)进入CAS操作时:线程进入 CAS 操作时,CPU 会再次读取变量当前的实际值 V,并与自己保留的预期值 E 做比较。如果相等,说明在读取到 V 到此刻之间,变量未被其他线程修改过,则执行写操作,将变量更新为 N。

事实上CAS 操作是一条由 CPU 硬件支持、原子的硬件指令,而这一条指令就可以完成上述伪代码的功能,所以它不会发生线程上下文切换或中断,保证了线程安全性

1.2 自旋

自旋(Spin)是指线程在尝试某项操作失败时,并不直接挂起或阻塞自己,而是在 CPU 上持续重试操作的一种行为。

在 CAS 操作中,自旋表现为:如果 CAS 更新失败,则再次获取当前值,并继续尝试 CAS,直到成功为止或超过重试上限。

自旋可以避免线程阻塞,但是也会带来性能问题:

  • CPU 空转,浪费资源:若竞争严重,线程会在 CPU 上不断循环,占用 CPU 时间片,导致资源紧张。
  • 没有退出机制可能导致活锁:如果没有设置合理的重试次数或退避机制,线程可能永远在重试,形成“活锁”。

所以通常会对自旋做出以下优化:

  • 设置最大重试次数
int retries = 0;
    while (!compareAndSet(expected, update) && retries++ < MAX_RETRIES) {
        // optional: yield(), sleep(), etc.
    }
  • 加入退避策略:在每次失败后,等待一小段时间再重试。
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10)); // 简单退避
  • 自适应自旋:JDK、HotSpot 等 JVM 实现中会根据前一次是否成功、CPU核数等因素动态调整是否自旋。

2.Atomic原子类

CAS 是一种由 CPU 提供的原子操作指令,那么我们该如何使用CAS机制呢?

在Java中我们是通过JDK提供的原子类(java.util.concurrent.atomic) 和JVM 的封装层(Unsafe)来间接使用这些指令。

Java 并发包中封装了多种 CAS 支持的原子类:

类名 支持的数据类型 特点
AtomicInteger int 最常用
AtomicLong long 高精度计数
AtomicBoolean boolean 并发状态标志
AtomicReference 对象引用 用于任意对象
AtomicStampedReference 对象 + 版本号 解决 ABA 问题
AtomicMarkableReference 对象 + 布尔标记 轻量状态管理

比如要把一个值为 0 的Integer变量修改为 1,就可以使用AtomicInteger来保证并发安全。

AtomicInteger atomicInt = new AtomicInteger(0);
boolean success = atomicInt.compareAndSet(0, 1);

compareAndSet是怎么实现的?

AtomicInteger 为例,其核心方法如下:

public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

这里的Unsafe是JVM 内部保留的,拥有直接操作内存、绕过访问控制、执行底层并发原语等能力,通过反射得到。

compareAndSwapInt(...) 是个 native 方法,会调用 JVM 的本地 C++ 实现(比如 HotSpot 中的 unsafe.cpp 文件,会映射到CPU的原子指令)。

native 方法 是 Java 中一种调用非 Java 语言实现的方法,通常用 C/C++ 编写。

![link-1][]原子类内部实现机制

不只是AtomicInteger,所有的 Java 原子类(如 AtomicInteger, AtomicReference)背后都调用了 Unsafe 提供的 compareAndSwapXXX() 方法。

3.版本号机制

3.1 为什么要引入版本号机制

CAS机制虽然基于CPU 原子指令实现了无所并发,但是也存在局限性:

(1)ABA问题CAS 本质只比较当前值是否与预期值相等。如果一个变量值被修改后又恢复为原值(例如 A → B → A),CAS 是无法感知这种“修改再还原”的过程的。

来看一个例子:假设有存款1000,此时有两个线程同时读取到账户余额为 1000,并准备各自扣除 500 元。

假设线程 t1 扣款成功后余额变为 500,但在 t2 执行前,账户又被其他转账操作充值500,存款变回 1000。此时线程 t2 仍然认为余额未变,继续扣款500,最终导致账户被错误扣款两次共计 1000 元

(2)只能作用于单一变量:CAS 操作仅支持单变量的原子性更新,不支持多个字段或对象的联合更新。

例如,如果需要同时更新一个对象的两个字段(如余额与积分),使用 CAS 无法保证这两个操作的一致性。

3.2 版本号机制执行流程

版本号机制是指在共享资源(如数据库记录或内存对象)中引入一个额外的 version 字段,用于标识该数据当前状态的“版本”。每次更新时,通过比较版本号判断数据是否在此期间被其他线程修改。

来举一个简单的例子说明版本号机制的执行流程。

假设我们有一个用户信息表,结构如下:

字段名
id 1001
email user@abc.com
version 1

(1)操作员 A 与操作员 B 同时读取用户信息:操作员 A 打算将邮箱改为 a@example.com,操作员 B 打算将邮箱改为 b@example.com,两人读取到的数据都是:email=user@abc.com, version=1

(2)操作员 A 修改后,执行更新语句:

UPDATE user
SET email = \'a@example.com\', version = version + 1
WHERE id = 1001 AND version = 1;

数据库记录被成功更新为:email =a@example.com,version = 2。

(3)操作员 B 随后也提交更新:

UPDATE user
SET email = \'b@example.com\', version = version + 1
WHERE id = 1001 AND version = 1;

由于数据库中当前版本号为 2,而 B 的更新条件仍然是 version = 1,所以这条 SQL 更新失败,B 的更改被驳回。

最后结果就是:操作员 A 的修改先提交成功,操作员 B 的提交被乐观锁拦截,避免了“后来者覆盖先提交者”的风险,系统数据保持一致,更新逻辑合理

这就相当于应用层实现了一种“版本比对的 CAS(Compare-And-Swap)”。

其实MySQL中的MVCC机制就是采用了基于版本控制的乐观锁思想。

未经允许不得转载:搜云库 » 乐观锁详解:CAS机制、Atomic原子类与版本号机制的原理与应用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们