Java学习-------------乐观锁和悲观锁

         乐观锁和悲观锁是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;}}}}