编程 乐观锁和悲观锁,如何区分?

2024-11-19 09:36:53 +0800 CST views 1235

乐观锁和悲观锁,如何区分?

悲观锁和乐观锁是两种常见的并发控制机制,用于处理多线程或多进程环境中的数据访问冲突问题。它们在数据库系统、分布式系统和多线程编程中都有广泛应用。本文将分析它们的原理、实现以及适用场景。

1. 悲观锁

1.1 定义

悲观锁(Pessimistic Lock)假设数据的访问会经常发生冲突,因此每次操作数据时,都会先对数据加锁,直到操作完成后才释放锁。在锁持有期间,其他线程无法访问这段数据,保证操作的独占性。

1.2 实现方式

  • 数据库中:悲观锁通常通过SQL语句实现,如 SELECT ... FOR UPDATE
  • 编程语言中:可以通过互斥锁(Mutex)或同步块(Synchronized Block)来实现悲观锁。

1.3 应用场景

悲观锁适用于并发冲突较多且需要严格保证数据一致性的场景,例如银行转账、库存扣减等操作。

1.4 优缺点

  • 优点:完全避免并发冲突,保证数据一致性和完整性。
  • 缺点:由于需要频繁加锁和解锁,性能开销较大,容易产生锁竞争和死锁问题。

1.5 示例

以下是使用Java和MySQL的悲观锁示例。假设有一个银行账户表,使用悲观锁确保在更新余额时不会发生并发修改。

1.5.1 数据库表结构

CREATE TABLE Account (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2) NOT NULL
);

1.5.2 Java实现

// Account类
public class Account {
    private int id;
    private BigDecimal balance;
    // Getters and Setters
}

// AccountMapper接口
public interface AccountMapper {
    Account getAccountByIdForUpdate(int id);
    void updateAccount(Account account);
}

// AccountService类
import org.springframework.transaction.annotation.Transactional;

public class AccountService {
    private AccountMapper accountMapper;

    public AccountService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    @Transactional
    public void updateAccountBalance(int accountId, BigDecimal amount) {
        // 获取账户信息并锁定记录
        Account account = accountMapper.getAccountByIdForUpdate(accountId);
        if (account == null) {
            throw new RuntimeException("Account not found");
        }

        // 更新余额
        account.setBalance(account.getBalance().add(amount));

        // 更新账户信息
        accountMapper.updateAccount(account);
    }
}

该示例中,FOR UPDATE用于锁定查询到的记录,确保操作的排他性。

1.6 注意事项

  • 事务管理:悲观锁需要与事务结合使用,锁在事务提交之前不会被释放。
  • 死锁风险:需要注意死锁的检测和处理。
  • 性能影响:每次加锁、解锁都会带来性能开销,特别是在高并发情况下。

2. 乐观锁

2.1 定义

乐观锁(Optimistic Lock)假设数据的并发冲突较少,不会主动加锁。在更新数据时,乐观锁会检测数据是否被其他线程修改过,如果发生冲突,则会重试操作或报错。

2.2 实现方式

  • 版本号机制:每次读取数据时获取版本号,更新数据时检查版本号是否变化,变化则表示数据已被修改。
  • 时间戳机制:使用时间戳检测数据是否被其他线程修改,原理类似于版本号机制。

2.3 应用场景

乐观锁适用于读多写少、并发冲突较少的场景,如用户评论系统、社交媒体点赞等。

2.4 优缺点

  • 优点:避免了加锁操作,性能较高,适合读多写少的场景。
  • 缺点:在高并发写操作场景下,频繁的重试可能影响性能。

2.5 示例

以下是使用乐观锁的Java示例。假设有一个银行账户表,包含账户ID、余额和版本号。

2.5.1 数据库表结构

CREATE TABLE Account (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2) NOT NULL,
    version INT NOT NULL
);

2.5.2 Java实现

// Account类
public class Account {
    private int id;
    private BigDecimal balance;
    private int version;
    // Getters and Setters
}

// AccountMapper接口
public interface AccountMapper {
    Account getAccountById(int id);
    int updateAccount(Account account);
}

// AccountService类
public class AccountService {
    private AccountMapper accountMapper;

    public AccountService(AccountMapper accountMapper) {
        this.accountMapper = accountMapper;
    }

    public void updateAccountBalance(int accountId, BigDecimal amount) {
        // 获取账户信息
        Account account = accountMapper.getAccountById(accountId);
        if (account == null) {
            throw new RuntimeException("Account not found");
        }

        // 记录当前版本号
        int currentVersion = account.getVersion();

        // 更新余额
        account.setBalance(account.getBalance().add(amount));
        // 更新版本号
        account.setVersion(currentVersion + 1);

        // 尝试更新账户信息
        int updatedRows = accountMapper.updateAccount(account);
        if (updatedRows == 0) {
            // 更新失败,可能是由于并发修改导致版本号不匹配
            throw new OptimisticLockException("Update failed due to concurrent modification");
        }
    }
}

在此示例中,更新时会检查数据库中的版本号,如果版本号与读取时一致,则更新成功,否则表示有其他线程修改了数据。

3. 区别总结

特性悲观锁乐观锁
假设前提假设冲突频繁发生,需加锁保护假设冲突不频繁,通过版本号或时间戳检测冲突
性能性能较低,需频繁加锁解锁性能较高,但高并发写操作下频繁重试影响性能
应用场景适用于并发冲突高、数据一致性要求严格的场景适用于并发冲突低、读多写少的场景

4. 总结

悲观锁和乐观锁是两种不同的并发控制机制。悲观锁通过加锁避免并发冲突,适合高冲突、高一致性要求的场景;乐观锁假设冲突较少,通过版本号或时间戳检测冲突,适合读多写少的场景。实际应用中,应根据业务场景选择合适的锁机制,以确保性能和数据一致性。

复制全文 生成海报 并发控制 数据库 多线程编程

推荐文章

记录一次服务器的优化对比
2024-11-19 09:18:23 +0800 CST
Go 接口:从入门到精通
2024-11-18 07:10:00 +0800 CST
18个实用的 JavaScript 函数
2024-11-17 18:10:35 +0800 CST
JavaScript 上传文件的几种方式
2024-11-18 21:11:59 +0800 CST
Vue中如何使用API发送异步请求?
2024-11-19 10:04:27 +0800 CST
前端如何一次性渲染十万条数据?
2024-11-19 05:08:27 +0800 CST
CSS 媒体查询
2024-11-18 13:42:46 +0800 CST
Rust 并发执行异步操作
2024-11-18 13:32:18 +0800 CST
2024年公司官方网站建设费用解析
2024-11-18 20:21:19 +0800 CST
FastAPI 入门指南
2024-11-19 08:51:54 +0800 CST
支付宝批量转账
2024-11-18 20:26:17 +0800 CST
CSS Grid 和 Flexbox 的主要区别
2024-11-18 23:09:50 +0800 CST
Vue3中如何实现状态管理?
2024-11-19 09:40:30 +0800 CST
百度开源压测工具 dperf
2024-11-18 16:50:58 +0800 CST
js迭代器
2024-11-19 07:49:47 +0800 CST
Nginx 反向代理 Redis 服务
2024-11-19 09:41:21 +0800 CST
12 个精选 MCP 网站推荐
2025-06-10 13:26:28 +0800 CST
程序员茄子在线接单