乐观锁是一种高效的并发控制机制,而CAS(Compare-And-Swap)是实现乐观锁的核心技术。下面我将详细介绍如何通过CAS操作实现乐观锁。
一、CAS操作原理
CAS(Compare-And-Swap)是一种原子操作,包含三个操作数:
- 内存位置(V)
- 预期原值(A)
- 新值(B)
当且仅当V的值等于A时,CAS才会将V的值更新为B,否则不做任何操作。无论是否更新成功,CAS都会返回V的当前值。
二、Java中的CAS支持
Java通过java.util.concurrent.atomic
包提供了CAS支持:
java
// AtomicInteger的CAS实现示例
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
三、基于CAS实现乐观锁
1. 简单计数器实现
java
public class OptimisticLockCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = count.get(); // 读取当前值
newValue = oldValue + 1; // 计算新值
} while (!count.compareAndSet(oldValue, newValue)); // CAS更新
}
public int getCount() {
return count.get();
}
}
2. 通用对象乐观锁实现
java
public class OptimisticLock<T> {
private AtomicReference<T> valueRef;
private AtomicInteger version = new AtomicInteger(0);
public OptimisticLock(T initialValue) {
this.valueRef = new AtomicReference<>(initialValue);
}
public void update(UnaryOperator<T> updateFunction) {
T oldValue;
T newValue;
int oldVersion;
int newVersion;
do {
oldValue = valueRef.get();
oldVersion = version.get();
newValue = updateFunction.apply(oldValue);
newVersion = oldVersion + 1;
} while (!(valueRef.compareAndSet(oldValue, newValue) &&
version.compareAndSet(oldVersion, newVersion)));
}
public T getValue() {
return valueRef.get();
}
}
四、CAS乐观锁的典型应用
1. 无锁栈实现
java
public class ConcurrentStack<E> {
private AtomicReference<Node<E>> top = new AtomicReference<>();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = top.get();
if (oldHead == null) {
return null;
}
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
final E item;
Node<E> next;
Node(E item) {
this.item = item;
}
}
}
2. 账户余额安全更新
java
public class BankAccount {
private AtomicInteger balance;
private AtomicInteger version = new AtomicInteger(0);
public BankAccount(int initialBalance) {
this.balance = new AtomicInteger(initialBalance);
}
public boolean transfer(int amount) {
int currentBalance;
int newBalance;
int currentVersion;
int newVersion;
do {
currentBalance = balance.get();
currentVersion = version.get();
if (currentBalance + amount < 0) { // 余额不足检查
return false;
}
newBalance = currentBalance + amount;
newVersion = currentVersion + 1;
} while (!(balance.compareAndSet(currentBalance, newBalance) &&
version.compareAndSet(currentVersion, newVersion)));
return true;
}
public int getBalance() {
return balance.get();
}
}
五、CAS乐观锁的优化技巧
1. 指数退避策略:减少高竞争下的CPU消耗
java
public boolean transferWithBackoff(int amount) {
int retries = 0;
final int MAX_RETRIES = 10;
final long BASE_DELAY_MS = 10;
while (retries < MAX_RETRIES) {
int currentBalance = balance.get();
int currentVersion = version.get();
if (currentBalance + amount < 0) {
return false;
}
if (balance.compareAndSet(currentBalance, currentBalance + amount) &&
version.compareAndSet(currentVersion, currentVersion + 1)) {
return true;
}
// 指数退避
try {
long delay = (long) (BASE_DELAY_MS * Math.pow(2, retries));
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
retries++;
}
return false;
}
2. 版本号压缩:将版本号和值打包到一个long中
java
public class CompactOptimisticLock {
private static final int VERSION_BITS = 32;
private AtomicLong state;
public CompactOptimisticLock(int initialValue) {
this.state = new AtomicLong(((long)initialValue << VERSION_BITS) | 0L);
}
public void update(UnaryOperator<Integer> updateFunction) {
long oldState;
long newState;
int oldValue;
int newValue;
int oldVersion;
int newVersion;
do {
oldState = state.get();
oldValue = (int)(oldState >>> VERSION_BITS);
oldVersion = (int)(oldState & 0xFFFFFFFFL);
newValue = updateFunction.apply(oldValue);
newVersion = oldVersion + 1;
newState = ((long)newValue << VERSION_BITS) | newVersion;
} while (!state.compareAndSet(oldState, newState));
}
public int getValue() {
return (int)(state.get() >>> VERSION_BITS);
}
}
六、CAS乐观锁的局限性及解决方案
-
ABA问题
• 问题描述:值从A变为B又变回A,CAS无法检测到中间变化
• 解决方案:使用
AtomicStampedReference
或AtomicMarkableReference
java
// 使用AtomicStampedReference解决ABA问题
public class ABASafeStack<E> {
private AtomicStampedReference<Node<E>> top =
new AtomicStampedReference<>(null, 0);
public void push(E item) {
Node<E> newHead = new Node<>(item);
int[] stampHolder = new int[1];
Node<E> oldHead;
int oldStamp;
do {
oldHead = top.get(stampHolder);
oldStamp = stampHolder[0];
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead, oldStamp, oldStamp + 1));
}
public E pop() {
int[] stampHolder = new int[1];
Node<E> oldHead;
Node<E> newHead;
int oldStamp;
do {
oldHead = top.get(stampHolder);
oldStamp = stampHolder[0];
if (oldHead == null) {
return null;
}
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead, oldStamp, oldStamp + 1));
return oldHead.item;
}
}
2. 循环时间长开销大
• 问题描述:高竞争下CAS可能长时间自旋
• 解决方案:结合线程让步或系统调度
java
public boolean transferWithYield(int amount) {
int currentBalance;
int newBalance;
int currentVersion;
int newVersion;
int spins = 0;
final int YIELD_THRESHOLD = 10;
do {
if (spins++ > YIELD_THRESHOLD) {
Thread.yield(); // 让出CPU时间片
spins = 0;
}
currentBalance = balance.get();
currentVersion = version.get();
if (currentBalance + amount < 0) {
return false;
}
newBalance = currentBalance + amount;
newVersion = currentVersion + 1;
} while (!(balance.compareAndSet(currentBalance, newBalance) &&
version.compareAndSet(currentVersion, newVersion)));
return true;
}
七、CAS乐观锁与数据库乐观锁对比
特性 | CAS乐观锁 | 数据库乐观锁 |
---|---|---|
作用范围 | 单个JVM进程内 | 跨进程、分布式环境 |
实现复杂度 | 相对简单 | 需要数据库支持 |
性能 | 极高(纳秒级) | 较高(微秒级) |
持久性 | 不持久 | 持久 |
ABA问题 | 存在 | 不存在 |
适用场景 | 内存数据结构、高并发计数器 | 分布式系统、数据库并发控制 |
八、总结
CAS操作是实现乐观锁的高效方式,具有以下特点:
- 无锁:避免线程阻塞和上下文切换
- 高性能:适合高并发场景
- 可扩展:可用于构建各种并发数据结构
在实际应用中,需要根据具体场景:
• 处理ABA问题
• 优化自旋策略
• 结合版本控制
• 必要时退化为悲观锁
通过合理使用CAS乐观锁,可以显著提高Java应用的并发性能和吞吐量。