在高并发场景下(比如商品库存扣减、账户余额扣减),并发控制是避免数据异常(如超卖、负库存)的关键手段。常见的实现方式主要有两类:

  • Java 层面的锁(synchronized、Lock)

  • 数据库层面的锁(悲观锁、乐观锁)

  • 分布式锁(Redis/Zookeeper)

下面结合实际案例(库存扣减),对这几种方式的实现和区别进行说明。

一、Java 层的锁

1.1 同步锁(synchronized)

synchronized 属于悲观锁,假设总是会有线程修改数据,所以每次访问都要加锁。

特点:

  • 锁住的是 JVM 内存中的对象。

  • 只能保证单机环境线程安全。

  • 无法解决分布式系统中的数据一致性问题。

public synchronized boolean reduceWithSynchronized(String productName, Integer amount) {
    Inventory inventory = inventoryRepository.findByProductName(productName)
            .orElseThrow(() -> new RuntimeException("商品不存在"));
    if (inventory.getTotalAmount() < amount) {
        return false;
    }
    inventory.setTotalAmount(inventory.getTotalAmount() - amount);
    inventoryRepository.save(inventory);
    return true;
}

1.2 显式锁(Lock 接口)

Java 并发包(java.util.concurrent.locks)提供了更灵活的锁实现,典型的是 ReentrantLock。

  • ReentrantLock(可重入锁)

    • 支持尝试加锁 tryLock()。

    • 支持超时加锁 tryLock(time, unit)。

    • 可以选择公平锁/非公平锁。

    • 提供条件变量 Condition,实现更精细的线程协作。

Lock lock = new ReentrantLock();

public void reduce() {
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

1.3 读写锁(ReadWriteLock)

  • 典型实现:ReentrantReadWriteLock。

  • 特点:

    • 多个读操作可以并发。

    • 写操作独占。

  • 适合 读多写少 的场景,比如缓存、配置管理。

1.4 原子类(CAS 乐观锁)

  • Java 并发包中的 原子类(AtomicInteger、AtomicLong、AtomicReference ...) 本质上是基于 CAS(Compare And Swap) 实现的乐观锁。

  • 不加锁,但通过 CPU 指令级别的比较交换保证线程安全。

1.5 CAS 的执行过程(举例)

假设有一个 AtomicInteger count = new AtomicInteger(5),线程想把值从 5 改成 6。

  1. 线程 A 读取到 内存值 V=5。

  2. 线程 A 认为预期值 E=5,准备更新为新值 N=6。

  3. CPU 执行 cmpxchg:

    • 如果内存中的值还是 5(V==E),则写入 6,返回 true。

    • 如果内存中的值已变(可能被线程 B 改成了 7),则写入失败,返回 false。

  4. 如果失败,线程 A 会重新读取最新值,再尝试 CAS → 形成 自旋。


1.6 CAS 的问题

虽然 CAS 非常高效,但也有一些典型问题:

  1. ABA 问题

    • 变量值从 A → B → A,CAS 看到值没变,但实际发生过修改。

    • 解决:AtomicStampedReference(带版本号) 或 LongAdder。

  2. 自旋开销大

    • 如果竞争激烈,CAS 会不断重试,消耗 CPU。

    • JDK8 提供了 LongAdder 等分段累加器,降低竞争。

  3. 只能保证一个变量的原子性

    • 如果需要多个变量同时 CAS → 需要加锁,或者用 AtomicReference 封装对象。

AtomicInteger stock = new AtomicInteger(100);

public boolean reduce(int amount) {
    while (true) {
        int current = stock.get();
        if (current < amount) {
            return false;
        }
        if (stock.compareAndSet(current, current - amount)) {
            return true;
        }
    }
}

二、数据库层的锁

2.1 悲观锁(Pessimistic Lock)

在查询时直接加锁(如 select ... for update),阻塞其他事务的修改操作。

特点:

  • 避免并发写冲突。

  • 性能差:会阻塞其他事务。

  • 适用于并发量不高、数据一致性要求严格的场景。

@Transactional(isolation = Isolation.READ_COMMITTED)
public boolean reduceWithPessimisticLock(String productName, Integer amount) {
    Inventory inventory = inventoryRepository.findByProductNameForUpdate(productName)
            .orElseThrow(() -> new RuntimeException("商品不存在"));
    if (inventory.getTotalAmount() < amount) {
        return false;
    }
    inventory.setTotalAmount(inventory.getTotalAmount() - amount);
    inventoryRepository.save(inventory);
    return true;
}

2.2 乐观锁(Optimistic Lock)

不加锁,读取时带上版本号(version),更新时判断版本号是否变化。

特点:

  • 并发高时性能更好。

  • 需要业务层处理失败的重试逻辑。

  • 常见实现:数据库字段 version + CAS。

@Transactional
public boolean reduceWithOptimisticLock(String productName, Integer amount) {
    Inventory inventory = inventoryRepository.findByProductName(productName)
            .orElseThrow(() -> new RuntimeException("商品不存在"));

    if (inventory.getTotalAmount() < amount) {
        return false;
    }

    int updatedRows = inventoryRepository.updateInventoryWithOptimisticLock(
            productName,
            inventory.getTotalAmount() - amount,
            inventory.getVersion()
    );

    if (updatedRows == 0) {
        throw new RuntimeException("库存扣减失败,请重试");
    }
    return true;
}

三、分布式锁(Redis 实现)

在分布式环境下,单机 synchronized 或数据库行锁无法保证不同服务节点的安全,这时常用 Redis 分布式锁。

特点:

  • 避免跨服务的数据冲突。

  • 性能高,但需要注意锁过期、续命、原子性问题。

public boolean reduceWithRedisLock(String productName, Integer amount) {
    String lockKey = "inventory_lock:" + productName;
    String requestId = UUID.randomUUID().toString();
    try {
        if (!redisLockService.tryLock(lockKey, requestId, 30)) {
            return false;
        }

        Inventory inventory = inventoryRepository.findByProductName(productName)
                .orElseThrow(() -> new RuntimeException("商品不存在"));

        if (inventory.getTotalAmount() < amount) {
            return false;
        }

        inventory.setTotalAmount(inventory.getTotalAmount() - amount);
        inventoryRepository.save(inventory);
        return true;
    } finally {
        redisLockService.releaseLock(lockKey, requestId);
    }
}

对比

方案分类优点缺点使用场景
synchronizedJava 悲观锁实现简单,线程安全单机有效,无法分布式单机小项目
数据库行锁数据库悲观锁简单可靠,事务内保证一致性阻塞性能差,可能死锁数据量小,写冲突少
乐观锁(version)数据库乐观锁高并发下性能好失败需要重试互联网库存扣减、余额扣减
Redis 分布式锁分布式悲观锁跨服务,性能较高需要解决续命和原子性问题分布式集群,高并发业务

更多推荐