乐观锁和悲观锁是2种设计思想,前者认为在操作数据的时候,其他人不会同时修改,而后者则认为操作数据时,会被其他人同时修改。
乐观锁通常是不上锁,先操作,再检查,即数据不会上锁,操作数据时无需进行获取锁和释放锁等操作,在操作完成之后进行检验,通过版本号,CAS或者其他方式判断是否被其他人操作过。而悲观锁就是先获取锁,再进行操作,操作结束后再释放锁,其他人才可以进行操作。
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁采用CAS时会有一个ABA问题,即如果一个链表,其原本构造为A->B->C,如果这个时候有一个线程对其进行操作,在A的前面添加一个B,变成B->A->B->C,但这个时候有一个新的线程也调用了这个数据,在他的前面添加了一个A,变成了A->A->B->C,且新的线程比旧的线程更早的完成了修改,那么这个时候进行检查时会发现头指针任然是A,那么他有可能会认为其没被修改过,导致出现数据错误。所以通常我们会用版本号和CAS混合使用的方法,避免这个错误。
下面是这两个锁的案例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 银行账户类(使用悲观锁)
*/
public class BankAccount {
private final String accountId;
private double balance;
private final Lock lock = new ReentrantLock(); // 悲观锁
public BankAccount(String accountId, double balance) {
this.accountId = accountId;
this.balance = balance;
}
/**
* 转账操作(悲观锁实现)
* @param toAccount 目标账户
* @param amount 转账金额
* @return 是否转账成功
*/
public boolean transfer(BankAccount toAccount, double amount) {
// 获取两个账户的锁(避免死锁,按固定顺序加锁)
Lock firstLock = this.accountId.compareTo(toAccount.accountId) < 0 ? this.lock : toAccount.lock;
Lock secondLock = this.accountId.compareTo(toAccount.accountId) < 0 ? toAccount.lock : this.lock;
firstLock.lock();
try {
secondLock.lock();
try {
if (this.balance < amount) {
return false;
}
// 执行转账
this.balance -= amount;
toAccount.balance += amount;
return true;
} finally {
secondLock.unlock();
}
} finally {
firstLock.unlock();
}
}
}
import java.util.concurrent.atomic.AtomicInteger;
/**
* 商品库存类(使用乐观锁)
*/
public class ProductInventory {
private final String productId;
private final AtomicInteger stock; // 使用原子类实现乐观锁
private final AtomicInteger version = new AtomicInteger(0); // 版本号
public ProductInventory(String productId, int stock) {
this.productId = productId;
this.stock = new AtomicInteger(stock);
}
/**
* 扣减库存(乐观锁实现)
* @param quantity 扣减数量
* @return 是否扣减成功
*/
public boolean deductStock(int quantity) {
while (true) {
int currentStock = stock.get();
int currentVersion = version.get();
if (currentStock < quantity) {
return false; // 库存不足
}
// CAS操作:比较库存和版本号,都未变时才更新
if (stock.compareAndSet(currentStock, currentStock - quantity) {
version.incrementAndGet(); // 更新版本号
return true;
}
// 如果CAS失败,说明有其他线程修改了库存,循环重试
}
}
/**
* 更完善的乐观锁实现(带版本号检查)
*/
public boolean deductStockWithVersion(int quantity) {
while (true) {
int currentStock = stock.get();
int currentVersion = version.get();
if (currentStock < quantity) {
return false;
}
// 同时检查库存和版本号
if (stock.compareAndSet(currentStock, currentStock - quantity) &&
version.compareAndSet(currentVersion, currentVersion + 1)) {
return true;
}
}
}
}