理解乐观锁 CAS 的非阻塞同步机制

在并发编程的世界里,“线程安全”通常意味着“加锁”。从 synchronized 关键字到 ReentrantLock,我们已经习惯了通过排他性地锁定资源来确保数据一致性。然而,锁并非万能良药。在追求极致性能的场景下,锁带来的上下文切换和等待成本可能成为系统瓶颈。那么,有没有一种方法可以在不挂起(阻塞)线程的情况下,安全地在多线程间修改共享变量呢? 答案是肯定的。这就是原子变量(atomic variables)和非阻塞同步算法(Non-blocking Algorithms),也就是我们俗称的“乐观锁”。 第一部分:锁的代价 在传统的并发模型中,我们主要依赖阻塞锁。这种机制也被称为悲观锁:它假设最坏的情况,认为如果不锁定资源,其他线程一定会捣乱。虽然锁能保证安全,但在高并发场景下,它带来了两个主要的性能杀手: 线程挂起与恢复的开销:当一个线程无法获取锁时,它会被阻塞(挂起)。操作系统在挂起和恢复线程时需要进行上下文切换,这在高位性能场景下是非常昂贵的。 活跃性问题:阻塞锁可能导致死锁、优先级倒置和线程饥饿。 对于简单的计数器自增操作,为此挂起和恢复线程就像为了喝杯水而关掉整个城市的供水系统一样——成本极度不成比例。对于涉及整数和对象引用变更的场景,我们需要一种更轻量、更“乐观”的方法。 第二部分:硬件级原子武器:CAS Java 5.0 引入了 java.util.concurrent 包。原子类(如 AtomicInteger)和非阻塞数据结构(如 ConcurrentLinkedQueue)性能飞跃背后的秘密武器就是 CAS (Compare-And-Swap) 指令。 什么是 CAS? CAS 是一种直接由 CPU 指令集支持的硬件原语(如 x86 的 lock cmpxchg)。它体现了乐观锁的哲学:我不加锁,直接尝试更新;如果更新失败(说明别人先到一步),我就重试或放弃,但我绝不挂起自己。 CAS 操作由三个核心步骤组成: 读取旧值:我看一眼内存里当前的值是多少。 比较:准备写入时,我再看一眼——内存里的值还是我刚才看到的那个吗? 交换:如果是,我就把它更新为新值(New Value);如果不是,说明在此期间有其他线程修改了它,我的操作失败。 我们可以用一段 Java 代码来模拟 CAS 逻辑(注意:真实的 CAS 是无锁的 CPU 指令,这里使用 synchronized 仅为了模拟其原子语义): @ThreadSafe public class SimulatedCAS { @GuardedBy("this") private int value; // 仅当内存中的值等于 expectedValue 时,才更新为 newValue public synchronized int compareAndSwap(int expectedValue, int newValue) { int oldValue = value; if (oldValue == expectedValue) value = newValue; return oldValue; } } 为什么 CAS 比锁快? ...

十二月 7, 2025 · 1 分钟