CAS、CAS自旋、CAS自旋锁、CLH锁与Java AQS:深入理解并发编程核心机制
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空转。
- ABA问题:值从A→B→A,CAS无法感知中间变化。
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
。 - 加锁流程:
- 线程创建一个节点,
locked
设为true
。 - 将前驱节点的
locked
字段作为自旋条件。 - 当前驱节点释放锁(
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
)的核心框架,用于构建锁和同步器(如ReentrantLock
、Semaphore
)。
- 核心思想:
- 通过一个
volatile int state
表示同步状态(如锁的重入次数、信号量许可数)。 - 使用CLH变体的双向队列(FIFO)管理等待线程。
- 通过一个
AQS的模板方法模式
AQS提供需子类实现的抽象方法(如tryAcquire
、tryRelease
),开发者只需关注状态管理逻辑。
示例:实现一个独占锁:
public class SimpleLock extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {return compareAndSetState(0, 1); // 通过CAS获取锁}@Overrideprotected boolean tryRelease(int arg) {setState(0); // 释放锁return true;}public void lock() {acquire(1);}public void unlock() {release(1);}
}
AQS的同步队列
- 队列节点(Node):
每个节点保存线程引用、等待状态(如CANCELLED
、SIGNAL
)和前驱/后继指针。 - 入队与出队:
- 线程获取锁失败时,被包装为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整合了这些机制,为高级同步器提供了灵活的实现框架。
理解这些机制的原理与实现,是编写高效、健壮并发代码的关键。