乐观锁与悲观锁

🔐 乐观锁 (Optimistic Lock) 与 悲观锁 (Pessimistic Lock)

在数据库、分布式系统及多线程编程中,乐观锁悲观锁是两种重要的并发控制机制。它们在数据一致性、性能及使用场景方面各有优势。


📌 一、核心概念

🔎 1. 悲观锁 (Pessimistic Lock)

“悲观”地认为数据会发生冲突,因此主动加锁以防止其他线程修改数据。

  • 每次访问数据时,都会先加锁,其他线程只能等待锁释放。
  • 适用于写操作频繁冲突较多的场景。
  • 常用于:数据库行锁文件锁Synchronized (Java) 等。

优点:数据一致性强,适用于竞争严重的场景。
❗️缺点:锁开销较大,容易导致性能瓶颈


🔎 2. 乐观锁 (Optimistic Lock)

“乐观”地认为数据冲突较少,因此不主动加锁,而是在更新数据时检测冲突。

  • 通常使用版本号 (Version) 或 时间戳 (Timestamp) 来判断数据是否被其他线程修改。
  • 适用于读操作频繁冲突较少的场景。
  • 常用于:CAS (Compare-And-Swap)数据库的 UPDATE WHERE 条件等。

优点:无锁操作,性能高,适用于竞争较少的场景。
❗️缺点:数据冲突时,重试成本较高。


📋 二、两者对比

特点悲观锁 (Pessimistic Lock)乐观锁 (Optimistic Lock)
加锁机制访问数据时主动加锁;访问数据时不加锁,更新时检查冲突;
性能并发量较低,线程/事务需等待锁释放;并发量较高,避免不必要的锁开销;
数据一致性数据一致性更强;可能存在更新失败的情况;
适用场景写多读少,数据冲突频繁;读多写少,数据冲突较少;
实现方式锁机制 (SynchronizedLock 等);版本号 (Version)、时间戳 (Timestamp)、CAS;

🚀 三、示例代码

🔥 1. 悲观锁示例 (MySQL 行锁)

在 MySQL 中,SELECT ... FOR UPDATE 会锁住查询到的行,防止其他事务修改。

-- 事务 1:获取悲观锁并更新数据
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- 事务 2:等待锁释放才能执行
START TRANSACTION;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;  -- 阻塞,等待事务 1 完成
UPDATE accounts SET balance = balance + 200 WHERE id = 1;
COMMIT;

优势:确保在整个事务中数据一致性强;
❗️劣势:锁的开销较大,易导致性能瓶颈。


🔥 2. 乐观锁示例 (MySQL 版本号)

通过版本号来判断数据是否被其他线程修改,避免加锁。

表结构

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2),
    version INT  -- 版本号字段
);

更新数据

-- 第一次读取数据 (版本号为 1)
SELECT balance, version FROM accounts WHERE id = 1;

-- 更新数据时检查版本号是否变化
UPDATE accounts 
SET balance = balance - 100, version = version + 1 
WHERE id = 1 AND version = 1;

-- 如果受影响行数为 0,表示版本号不匹配,更新失败,需重试

优势:无锁操作,性能高;
❗️劣势:数据冲突时,重试成本较高。


🔥 3. Java 中的悲观锁示例 (Synchronized)

public class PessimisticLockExample {
    private int count = 0;

    // 使用 synchronized 作为悲观锁
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }

    public static void main(String[] args) {
        PessimisticLockExample example = new PessimisticLockExample();

        Thread t1 = new Thread(example::increment);
        Thread t2 = new Thread(example::increment);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("最终结果:" + example.getCount());
    }
}

输出示例

最终结果:2

🔥 4. Java 中的乐观锁示例 (CAS - Compare-And-Swap)

import java.util.concurrent.atomic.AtomicInteger;

public class OptimisticLockExample {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        int current;
        do {
            current = count.get();
        } while (!count.compareAndSet(current, current + 1));
    }

    public int getCount() {
        return count.get();
    }

    public static void main(String[] args) {
        OptimisticLockExample example = new OptimisticLockExample();

        Thread t1 = new Thread(example::increment);
        Thread t2 = new Thread(example::increment);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("最终结果:" + example.getCount());
    }
}

输出示例

最终结果:2

🔎 CAS 操作会不断尝试更新值,直至成功,避免了锁的阻塞。


📌 四、应用场景总结

场景推荐使用
频繁写操作,竞争激烈✅ 悲观锁
读多写少,数据冲突少✅ 乐观锁
数据一致性要求极高 (如金融系统)✅ 悲观锁
高性能要求,减少锁开销✅ 乐观锁
高并发场景下的短时间冲突✅ 乐观锁

🌟 总结

  • 悲观锁更注重数据的一致性,适用于写多读少冲突频繁的场景;
  • 乐观锁更注重性能,适用于读多写少冲突较少的场景;
  • 乐观锁不使用锁机制,避免了线程阻塞,性能更优,但需处理冲突重试问题;
  • 悲观锁在竞争严重时更稳定,适用于关键业务。

💡 在选择锁机制时,需根据具体业务场景、数据访问模式及性能需求来选择最佳策略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值