乐观锁和悲观锁

在并发编程和数据库操作中,锁机制是确保数据一致性的重要手段。乐观锁和悲观锁作为两种主流的并发控制策略,各自适用于不同的业务场景。本文将剖析这两种锁机制的核心思想、技术实现和典型应用,帮助开发者做出合理的技术选型。

基本概念与核心思想

1. 悲观锁:保守的独占策略

悲观锁基于"最坏情况"假设,认为并发操作中数据冲突必然发生。其核心机制是:访问数据前先加锁,确保整个操作过程中资源被独占。

典型特征:

  1. 获取锁后才能操作数据
  2. 其他线程必须阻塞等待
  3. 适合写操作频繁的场景
  4. 实现简单但性能开销大

2. 乐观锁:乐观的冲突检测

乐观锁则持相反观点,假设多数情况下不会发生冲突。它不提前加锁,只在数据提交时检查是否被其他线程修改。

典型特征:

  1. 读取数据时不加锁
  2. 提交时通过版本号/CAS检测冲突
  3. 冲突时回滚或重试
  4. 适合读多写少的场景

技术实现对比

悲观锁实现方式

在Java中,synchronize 和 ReentrantLock都是悲观锁的实现

// synchronized关键字
public synchronized void update() {
    // 临界区代码
// ReentrantLock显式锁

private Lock lock = new ReentrantLock();
public void update() {
    lock.lock();
    try {
        // 临界区代码
finally {

        lock.unlock();
}

乐观锁实现方式

(1) 版本号机制

-- 数据库表添加version字段
UPDATE products 
SET stock=stock-1, version=version+1 
WHERE id=1 AND version=old_version;

(2) CAS原子操作

// Java Atomic类
AtomicInteger counter = new AtomicInteger(0);

public void increment() {
    while(true) {
        int current = counter.get();
        int next = current + 1;
        if(counter.compareAndSet(current, next)) {
            break;
        }
    }
}

(3) 技术对比表

特性

悲观锁

乐观锁

加锁时机

操作前加锁

提交时验证

实现复杂度

简单

需处理冲突重试

性能开销

高(锁竞争)

低(无阻塞)

数据一致性

强一致性

最终一致性

适用场景

写多读少

读多写少

典型实现

synchronized

ReentrantLock

版本号、CAS

应用场景分析

悲观锁适用场景

  1. 金融交易系统:如账户余额修改,要求强一致性
  2. 库存实时扣减:避免超卖现象
  3. 长事务处理:操作过程需要保持数据状态
  4. 临界资源保护:如全局配置修改

乐观锁适用场景

  1. 商品详情页:大量并发读取,少量评价更新
  2. 文档协作编辑:多人同时编辑不同部分
  3. 统计计数器:如文章阅读量统计
  4. 版本控制系统:如Git、SVN的合并机制

经典案例对比

电商库存管理:

  • 悲观锁方案:
BEGIN;
  SELECT stock FROM products WHERE id=1 FOR UPDATE;
  -- 检查库存
  UPDATE products SET stock=stock-1 WHERE id=1;
  COMMIT;

优点:能够尽可能的避免超卖

缺点:高并发时性能急剧下降

  • 乐观锁方案:
  UPDATE products 
  SET stock=stock-1, version=version+1 
  WHERE id=1 AND version=old_version AND stock>0;

优点:支持更高并发

缺点:失败需前端重试

进阶问题与解决方案

1. 乐观锁的ABA问题

问题描述:

变量值从A改为B又改回A,CAS检查会误判无修改。

解决方案:

  1. 使用AtomicStampedReference添加版本戳
  2. 数据库使用自增版本号而非时间戳

2. 高冲突下的优化策略

  1. 指数退避:冲突后延迟重试,避免活锁
  2. 熔断机制:冲突超过阈值转为悲观锁
  3. 批量合并:累计多次更新一次性提交

3. 分布式锁实现

3.1. 基于Redis的悲观锁(Redisson)

核心机制:

// 加锁逻辑
SET lock_key client_id NX PX 30000
// 看门狗自动续期(默认30秒续期一次)

高级特性:

  1. 可重入锁:同一线程可重复获取锁
  2. 红锁算法:跨多个Redis节点实现容错
  3. 公平锁:按请求顺序分配锁

代码示例:

RLock lock = redissonClient.getLock("payment_lock");
try {
    if(lock.tryLock(10, 30, TimeUnit.SECONDS)) {
        // 业务处理
    }
} finally {
    lock.unlock();
}

3.2. 分布式乐观锁实现

  1. Redis WATCH/MULTI/EXEC指令
  • 实现流程:
WATCH resource_key
current_value = GET resource_key
MULTI
SET resource_key new_value
EXEC  # 若key被修改则返回nil
  • 适用场景:简单数据结构的原子更新
  1. 增强版实现方案
  • 版本号机制:
UPDATE products 
SET stock=stock-1, version=version+1 
WHERE id=1 AND version=old_version
  • Redis Lua脚本:
local key = KEYS[1]
local version = ARGV[1]
if redis.call("get", key.."_version") == version then
  redis.call("set", key.."_version", version+1)
  return redis.call("incrby", key, -1)
end
return false

实践建议

在选择使用时需要注意事项如下

  1. 评估冲突概率:低冲突(<20%)优先乐观锁,高冲突选择悲观锁
  2. 监控关键指标:包括锁等待时间、冲突重试次数等
  3. 合理设置超时:避免线程长时间阻塞
  4. 结合业务特点:如金融系统倾向悲观锁,社交 feed 流倾向乐观锁

小结

乐观锁和悲观锁各有优劣,没有绝对的好坏之分。技术选型的核心在于理解业务场景:对数据一致性要求极高的场景适合悲观锁;追求高吞吐、能容忍偶尔重试的场景则乐观锁更优。分布式系统中,往往需要两者结合使用,如用乐观锁处理大部分请求,对特定关键操作采用悲观锁保护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值