CAS、CAS自旋、CAS自旋锁、CLH锁与Java AQS:深入理解并发编程核心机制


1. CAS(Compare and Swap)

什么是CAS?

CAS(Compare and Swap)是一种无锁(Lock-Free)的原子操作,用于在多线程环境下实现数据同步。其核心思想是:

  • 比较内存中的值与预期值是否一致,若一致则更新为新值,否则不修改。
  • 整个过程是原子的,由底层硬件(如CPU的CMPXCHG指令)直接支持。
CAS在Java中的实现

Java通过Unsafe类和原子类(如AtomicInteger)提供CAS支持:

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

示例

AtomicInteger count = new AtomicInteger(0);
count.compareAndSet(0, 1); // 成功将0改为1
CAS的优缺点
  • 优点
    • 避免锁竞争,减少线程切换开销。
    • 适用于低冲突场景(如计数器、标志位更新)。
  • 缺点
    • ABA问题:值从A→B→A,CAS无法感知中间变化。
      解决方案:使用版本号(如AtomicStampedReference)。
    • 自旋开销:长时间失败可能导致CPU空转。

2. CAS自旋与CAS自旋锁

CAS自旋(Spin)

CAS自旋是指线程在CAS失败后循环重试,直到成功。
示例:实现一个简单的自增操作:

public class SpinCounter {
    private AtomicInteger value = new AtomicInteger(0);
    
    public void increment() {
        int oldVal;
        do {
            oldVal = value.get();
        } while (!value.compareAndSet(oldVal, oldVal + 1));
    }
}
CAS自旋锁(Spin Lock)

基于CAS自旋实现的锁,线程在获取锁失败时不会阻塞,而是循环尝试。
示例

public class SpinLock {
    private AtomicBoolean locked = new AtomicBoolean(false);
    
    public void lock() {
        while (!locked.compareAndSet(false, true)) {
            // 自旋等待
        }
    }
    
    public void unlock() {
        locked.set(false);
    }
}

缺点

  • 高竞争下CPU利用率高,适用于临界区极短的场景(如操作系统内核)。

3. CLH锁

CLH锁的设计思想

CLH(Craig, Landin, Hagersten)锁是一种基于链表的队列锁,通过隐式队列管理线程,减少缓存一致性流量。

  • 核心结构:每个线程持有一个节点(Node),包含一个volatile布尔字段locked
  • 加锁流程
    1. 线程创建一个节点,locked设为true
    2. 将前驱节点的locked字段作为自旋条件。
    3. 当前驱节点释放锁(locked=false),当前线程获取锁。
CLH锁的Java伪代码
public class CLHLock {
    private static class Node {
        volatile boolean locked = true;
    }
    
    private final ThreadLocal<Node> myNode = ThreadLocal.withInitial(Node::new);
    private final AtomicReference<Node> tail = new AtomicReference<>();
    
    public void lock() {
        Node node = myNode.get();
        Node pred = tail.getAndSet(node);
        while (pred != null && pred.locked) {
            // 自旋等待前驱节点释放锁
        }
    }
    
    public void unlock() {
        myNode.get().locked = false;
        myNode.remove(); // 清理线程本地状态
    }
}
CLH锁的优势
  • 公平性:严格按请求顺序获取锁。
  • 低缓存一致性开销:线程仅监视前驱节点的状态,减少总线风暴。

4. Java AQS(AbstractQueuedSynchronizer)

AQS的核心设计

AQS是Java并发包(java.util.concurrent)的核心框架,用于构建锁和同步器(如ReentrantLockSemaphore)。

  • 核心思想
    • 通过一个volatile int state表示同步状态(如锁的重入次数、信号量许可数)。
    • 使用CLH变体的双向队列(FIFO)管理等待线程。
AQS的模板方法模式

AQS提供需子类实现的抽象方法(如tryAcquiretryRelease),开发者只需关注状态管理逻辑。
示例:实现一个独占锁:

public class SimpleLock extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        return compareAndSetState(0, 1); // 通过CAS获取锁
    }
    
    @Override
    protected boolean tryRelease(int arg) {
        setState(0); // 释放锁
        return true;
    }
    
    public void lock() {
        acquire(1);
    }
    
    public void unlock() {
        release(1);
    }
}
AQS的同步队列
  • 队列节点(Node)
    每个节点保存线程引用、等待状态(如CANCELLEDSIGNAL)和前驱/后继指针。
  • 入队与出队
    • 线程获取锁失败时,被包装为Node加入队列尾部。
    • 前驱节点释放锁后,唤醒后继节点。
AQS与CLH锁的关系

AQS的同步队列是CLH锁的变体:

  • 将CLH的隐式队列改为显式的双向链表。
  • 支持中断、超时和条件变量(Condition)。

5. 对比总结

机制 特点 适用场景
CAS 无锁原子操作,依赖硬件指令 简单状态更新(如计数器)
CAS自旋锁 基于CAS的自旋,无队列管理 临界区极短,低竞争环境
CLH锁 公平锁,基于隐式队列,减少缓存一致性流量 高并发公平锁需求
AQS 提供同步框架,支持独占/共享模式、条件变量 构建复杂同步器(如ReentrantLock)

6. 实际应用

  • ReentrantLock:基于AQS实现可重入锁,支持公平/非公平模式。
  • Semaphore:通过AQS管理许可数量。
  • CountDownLatch:利用AQS的共享模式实现线程协作。

7. 性能优化与注意事项

  • 减少CAS竞争:通过分段锁(如ConcurrentHashMap)或缓存行填充(避免伪共享)。
  • 避免长时间自旋:结合自旋与阻塞(如AQS的自旋后挂起)。
  • 条件变量的正确使用:始终在循环中检查条件(防止虚假唤醒)。

8. 总结

CAS、自旋锁、CLH锁和AQS构成了Java并发编程的底层基石:

  • CAS提供了无锁编程的基础。
  • 自旋锁优化了短临界区的性能。
  • CLH锁通过队列化降低了竞争开销。
  • AQS整合了这些机制,为高级同步器提供了灵活的实现框架。

理解这些机制的原理与实现,是编写高效、健壮并发代码的关键。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐