3.4 乐观锁解决超卖问题

乐观锁解决超卖问题

修改代码方案一、

VoucherOrderServiceImpl 在扣减库存时,改为:

boolean success = seckillVoucherService.update() 
            .setSql("stock= stock -1") //set stock = stock -1
            .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?

以上逻辑的核心含义是:只要我扣减库存时的库存和之前我查询到的库存是一样的,就意味着没有人在中间修改过库存,那么此时就是安全的,但是以上这种方式通过测试发现会有很多失败的情况,失败的原因在于:在使用乐观锁过程中假设100个线程同时都拿到了100的库存,然后大家一起去进行扣减,但是100个人中只有1个人能扣减成功,其他的人在处理时,他们在扣减时,库存已经被修改过了,所以此时其他线程都会失败

修改代码方案二、

之前的方式要修改前后都保持一致,但是这样我们分析过,成功的概率太低,所以我们的乐观锁需要变一下,改成stock大于0 即可

boolean success = seckillVoucherService.update()
            .setSql("stock= stock -1")
            .eq("voucher_id", voucherId).update().gt("stock",0); //where id = ? and stock > 0

知识小扩展:

针对cas中的自旋压力过大,我们可以使用Longaddr这个类去解决

Java8 提供的一个对AtomicLong改进后的一个类,LongAdder

大量线程并发更新一个原子性的时候,天然的问题就是自旋,会导致并发性问题,当然这也比我们直接使用syn来的好

所以利用这么一个类,LongAdder来进行优化

如果获取某个值,则会对cell和base的值进行递增,最后返回一个完整的值

3.4 乐观锁解决超卖问题_第1张图片

乐观锁与CAS机制

乐观锁(Optimistic Locking)是一种在并发编程中常用的锁机制,它基于一个假设:大多数情况下,数据在读取和修改的过程中不会发生冲突。因此,乐观锁不会在操作数据时直接加锁,而是通过某种机制(如版本号或条件判断)来检测冲突。如果检测到冲突,则会采取相应的措施(如重试或回滚)。

在乐观锁的实现方式中,CAS(Compare-And-Swap,比较并交换) 是一种非常重要的机制,广泛应用于无锁编程(Lock-Free Programming)中。


1. CAS机制的原理

CAS是一种原子操作,它包含三个参数:

  1. 内存值(Memory Value):需要操作的内存地址的当前值。
  2. 预期值(Expected Value):操作前预期的内存值。
  3. 新值(New Value):如果预期值与内存值一致,则将内存值更新为新值。

CAS操作的逻辑如下:

  • 如果内存值与预期值一致,说明在这段时间内没有其他线程修改过该值,因此可以安全地将内存值更新为新值。
  • 如果内存值与预期值不一致,说明有其他线程修改了该值,当前操作失败。

CAS操作通常由硬件指令支持,确保操作的原子性,因此不会被其他线程中断。


2. CAS在乐观锁中的应用

在乐观锁中,CAS机制常用于实现无锁的并发控制。它通过版本号或直接操作内存值来检测冲突。以下是两种常见的应用场景:

2.1 基于版本号的乐观锁

在数据库或内存中为每个数据项维护一个版本号(version字段)。每次更新数据时,版本号会递增。CAS机制用于确保在更新操作中,版本号没有被其他线程修改。

int currentVersion = getCurrentVersion();
int newVersion = currentVersion + 1;

if (compareAndSwapVersion(currentVersion, newVersion)) {
    // 更新成功
    updateData();
} else {
    // 更新失败,重试或回滚
}
2.2 基于内存值的CAS

直接对内存值进行操作。例如,Java中的AtomicInteger类使用CAS机制来实现线程安全的整数操作。

AtomicInteger counter = new AtomicInteger(0);

boolean success = counter.compareAndSet(0, 1); // 如果当前值为0,则更新为1
if (success) {
    System.out.println("更新成功");
} else {
    System.out.println("更新失败");
}

3. CAS的优点

  1. 高性能
    • CAS操作是无锁的,不会导致线程阻塞,因此在高并发场景下性能较高。
    • 避免了锁的开销,减少了上下文切换的频率。
  2. 减少锁竞争
    • 由于CAS操作是原子的,多个线程可以同时尝试更新,只有在冲突时才会失败。
    • 适用于冲突概率较低的场景。
  3. 线程安全
    • CAS操作由硬件指令支持,确保了操作的原子性,因此是线程安全的。

4. CAS的缺点

  1. ABA问题
    • 如果一个变量的值在操作过程中被修改了多次,最终又恢复到了原始值,CAS操作可能会误认为没有冲突。例如,线程A读取到值A,线程B将值修改为B,再修改回A,线程A的CAS操作会成功,但实际上数据已经被修改过。
    • 解决ABA问题的一种方法是引入版本号或时间戳,确保每次修改都会改变版本号。
  2. 高冲突场景下的性能问题
    • 如果冲突概率较高(即多个线程频繁修改同一变量),CAS操作可能会失败多次,导致大量重试,反而降低性能。
    • 在这种情况下,可能需要结合其他机制(如退避算法)来优化性能。
  3. 适用范围有限
    • CAS主要用于单个变量的原子操作,对于复杂的数据结构(如链表或树)或需要多个变量一致性的场景,CAS可能无法直接应用。

5. CAS在Java中的实现

Java提供了java.util.concurrent.atomic包,其中包含了一系列基于CAS机制的原子类,如AtomicIntegerAtomicLongAtomicReference等。这些类通过CAS操作实现了线程安全的变量更新。

示例:使用AtomicInteger
public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        // 使用CAS操作递增计数器
        while (true) {
            int current = count.get();
            int next = current + 1;
            if (count.compareAndSet(current, next)) {
                break; // 更新成功,退出循环
            }
        }
    }

    public int get() {
        return count.get();
    }
}
示例:解决ABA问题

可以通过引入版本号或使用AtomicStampedReference来解决ABA问题。AtomicStampedReference为每个值维护一个版本号(戳),确保即使值被修改多次,版本号也会递增。

AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0);

int[] stampHolder = new int[1];
int current = ref.get(stampHolder);
int stamp = stampHolder[0];

if (ref.compareAndSet(current, 1, stamp, stamp + 1)) {
    System.out.println("更新成功");
} else {
    System.out.println("更新失败");
}

6. CAS与乐观锁的结合

在秒杀系统中,CAS机制可以用于实现乐观锁,确保库存扣减操作的线程安全性。例如:

public boolean deductStock(int voucherId) {
    SeckillVoucher voucher = getSeckillVoucher(voucherId);
    int currentStock = voucher.getStock();
    if (currentStock <= 0) {
        return false; // 库存不足
    }

    // 使用CAS机制扣减库存
    if (compareAndSwapStock(voucherId, currentStock, currentStock - 1)) {
        return true; // 扣减成功
    } else {
        return false; // 扣减失败,重试或返回失败
    }
}

通过这种方式,CAS机制可以在高并发场景下有效减少锁的竞争,同时确保操作的原子性。


7. 总结

CAS机制是乐观锁的一种重要实现方式,适用于冲突概率较低的场景。它通过原子操作确保数据的一致性,同时避免了锁的开销,提高了并发性能。然而,CAS也存在ABA问题和高冲突场景下的性能瓶颈。在实际应用中,可以根据具体需求选择合适的锁机制,或结合其他技术(如版本号、退避算法)来优化性能。

你可能感兴趣的:(Redis,java,redis)