轻量级锁的CAS操作实现原理

186 阅读6分钟

轻量级锁的CAS操作实现原理

让我详细解释轻量级锁的CAS操作实现原理。我会从简单到复杂逐步展开。

1. CAS基本概念

首先,让我们理解什么是CAS(Compare And Swap)。想象你在改一份共享文档:

// CAS的基本思想
class Document {
    private int version;     // 文档版本号
    private String content;  // 文档内容
    
    boolean updateContent(int expectedVersion, String newContent) {
        // 检查版本号是否匹配,如果匹配才更新
        if (version == expectedVersion) {
            content = newContent;
            version++;
            return true;
        }
        return false;
    }
}

这就像你在修改文档前先确认没人改过它,这个过程必须是原子的(不可分割的)。

2. 轻量级锁的实现步骤

让我们详细看看轻量级锁获取的完整过程:

public class LightweightLockExample {
    private Integer value = 0;  // 要同步的对象
    
    public void synchronizedMethod() {
        synchronized(value) {  // 这里会触发轻量级锁
            // 临界区代码
        }
    }
}

实现过程分为以下几步:

2.1 创建锁记录(Lock Record)

// 在线程栈中创建锁记录(简化的代码示意)
class LockRecord {
    private Object owner;          // 持有的对象
    private long originalMarkWord; // 对象原始的Mark Word
    
    public LockRecord(Object obj) {
        this.owner = obj;
        this.originalMarkWord = obj.markWord;
    }
}

每个线程在执行同步代码时,会在自己的栈帧中创建一个锁记录空间。

2.2 CAS替换对象头

接下来是最关键的CAS操作:

class LightweightLocking {
    // 用本地方法实现CAS操作
    native boolean compareAndSwapMarkWord(Object obj, 
                                        long expectedValue,
                                        long newValue);
    
    boolean acquireLock(Object obj) {
        // 创建锁记录
        LockRecord lockRecord = createLockRecord(obj);
        
        // 构造新的Mark Word
        // 其中包含了指向Lock Record的指针
        long newMarkWord = constructMarkWord(lockRecord);
        
        // CAS替换对象头的Mark Word
        return compareAndSwapMarkWord(obj, 
                                    lockRecord.originalMarkWord,
                                    newMarkWord);
    }
}

在底层,这个CAS操作会被转换为CPU的原子指令:

; x86架构下的CAS指令示意
; cmpxchg指令实现原子的比较和交换
mov     rax, [expectedValue]   ; 加载期望值
lock cmpxchg [address], rbx    ; 原子地比较和交换

2.3 处理竞争情况

当CAS操作失败时,需要处理锁升级:

class LockUpgrade {
    void handleLockContention(Object obj) {
        // 先尝试自旋
        for (int i = 0; i < MAX_SPIN; i++) {
            if (tryAcquireLock(obj)) {
                return;
            }
            // 自旋等待
            Thread.onSpinWait();  // CPU提示,优化自旋
        }
        
        // 自旋失败,升级为重量级锁
        inflateToHeavyweight(obj);
    }
}

3. JVM中的具体实现

在HotSpot VM中,轻量级锁的实现位于synchronizer.cpp

// HotSpot中的轻量级锁获取过程(简化版)
bool ObjectSynchronizer::tryAcquireLock(oop obj) {
    // 获取对象的Mark Word
    markOop mark = obj->mark();
    
    // 如果已经是轻量级锁
    if (mark->is_locked() && !mark->is_heavyweight()) {
        // 检查是否是当前线程的锁记录
        if (mark->locker() == thread->lock_record()) {
            // 重入计数增加
            thread->lock_record()->inc_recursions();
            return true;
        }
    }
    
    // 创建新的锁记录
    BasicLockObject* lock = thread->lock_record();
    lock->set_obj(obj);
    lock->set_displaced_header(mark);
    
    // 尝试CAS替换Mark Word
    if (Atomic::cmpxchg(lock, obj->mark_addr(), mark) == mark) {
        return true;
    }
    
    // CAS失败,处理竞争
    return handleLockContention(obj, lock);
}

4. 性能考虑

轻量级锁的CAS实现考虑了多个性能因素:

class LockPerformance {
    // 缓存行对齐
    @sun.misc.Contended  // 防止伪共享
    private static class PaddedLock {
        volatile long markWord;
        // 填充以避免临近变量的缓存行冲突
        long p1, p2, p3, p4, p5, p6, p7;
    }
    
    // 自旋优化
    private static final int SPIN_LIMIT = 50;
    
    // 重入优化
    private static class LockRecord {
        int recursionCount;  // 重入计数
        // ... 其他字段
    }
}

5. 实际应用示例

让我们看一个完整的例子:

public class OptimisticLocking {
    private volatile long value;
    
    public void optimisticUpdate(long newValue) {
        long current;
        do {
            // 读取当前值
            current = value;
            // 如果没人修改过,就更新成功
        } while (!compareAndSet(current, newValue));
    }
    
    // 底层调用Unsafe的CAS操作
    private native boolean compareAndSet(long expected, long newValue);
}

这种实现方式:

  1. 不需要重量级的监视器
  2. 适合竞争不激烈的场景
  3. 失败时可以快速重试
  4. 避免了线程调度的开销

通过这些机制,轻量级锁在大多数情况下都能提供比重量级锁更好的性能。您对哪部分还有疑问吗?欢迎评论区讨论。

面试小贴士

以下是几个关于轻量级锁和CAS的高频面试题,每个问题后面都附有简要答案:

  1. 什么是CAS?请解释其基本原理。

    答:CAS是Compare And Swap的缩写,是一种无锁算法。它包含三个操作数——内存位置、预期原值及新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值,否则不做任何操作。

  2. CAS在Java中的应用有哪些?

    答:在Java中,CAS主要有以下应用:

    • Atomic包中的原子操作类,如AtomicInteger、AtomicBoolean等
    • 并发容器如ConcurrentHashMap
    • JVM中的轻量级锁实现
  3. 轻量级锁的获取过程是怎样的?为什么要用CAS?

    答:轻量级锁的获取分为三步:

    1. 在当前线程的栈帧中创建锁记录(Lock Record)
    2. 将锁记录中的Lock Word复制到对象头的Mark Word中
    3. 用CAS操作尝试将对象头的Mark Word替换为指向锁记录的指针

    使用CAS是为了在多线程竞争的情况下,确保只有一个线程能成功获得锁。

  4. 如果CAS操作失败了会怎样?轻量级锁会如何处理?

    答:如果CAS操作失败,说明有其他线程同时在尝试获取该锁。这时,当前线程会先自旋一段时间,尝试再次获取锁。如果自旋到一定次数还没获取到,轻量级锁会膨胀为重量级锁,进入重量级锁的流程。

  5. 什么是锁膨胀?什么情况下会发生?

    答:锁膨胀是指轻量级锁升级为重量级锁的过程。当有多个线程同时竞争轻量级锁,导致自旋超过一定次数时,就会发生锁膨胀。膨胀后,线程将进入Monitor的EntryList队列,等待被唤醒。

  6. 轻量级锁的实现考虑了哪些性能因素?

    答:主要有以下几个考虑:

    • 空间局部性:Lock Record在线程栈上分配,访问速度快
    • 缓存行填充:避免false sharing带来的性能下降
    • 减少线程切换:CAS操作失败时先自旋,而不是直接阻塞
    • 重入优化:每个Lock Record中都有一个recursion字段,记录重入次数
  7. CAS操作有哪些潜在的问题?

    答:CAS操作主要有三个问题:

    • ABA问题:一个值从A变为B又变回A,CAS会误认为它没有变化
    • 循环时间长开销大:如果CAS失败,会一直循环尝试,耗费CPU
    • 只能保证一个共享变量的原子操作:对多个共享变量操作时,CAS无法保证原子性