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

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

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

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

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. 总结

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

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

推荐文章

纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
解决 PHP 中的 HTTP 请求超时问题
2024-11-19 09:10:35 +0800 CST
npm速度过慢的解决办法
2024-11-19 10:10:39 +0800 CST
PHP设计模式:单例模式
2024-11-18 18:31:43 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
2025,重新认识 HTML!
2025-02-07 14:40:00 +0800 CST
Manticore Search:高性能的搜索引擎
2024-11-19 03:43:32 +0800 CST
内网穿透技术详解与工具对比
2025-04-01 22:12:02 +0800 CST
Golang中国地址生成扩展包
2024-11-19 06:01:16 +0800 CST
利用Python构建语音助手
2024-11-19 04:24:50 +0800 CST
WebSQL数据库:HTML5的非标准伴侣
2024-11-18 22:44:20 +0800 CST
php客服服务管理系统
2024-11-19 06:48:35 +0800 CST
Go语言中实现RSA加密与解密
2024-11-18 01:49:30 +0800 CST
windows下mysql使用source导入数据
2024-11-17 05:03:50 +0800 CST
Web 端 Office 文件预览工具库
2024-11-18 22:19:16 +0800 CST
Python Invoke:强大的自动化任务库
2024-11-18 14:05:40 +0800 CST
介绍Vue3的静态提升是什么?
2024-11-18 10:25:10 +0800 CST
Vue3中的Scoped Slots有什么改变?
2024-11-17 13:50:01 +0800 CST
Golang 几种使用 Channel 的错误姿势
2024-11-19 01:42:18 +0800 CST
禁止调试前端页面代码
2024-11-19 02:17:33 +0800 CST
2024年公司官方网站建设费用解析
2024-11-18 20:21:19 +0800 CST
JavaScript 实现访问本地文件夹
2024-11-18 23:12:47 +0800 CST
程序员茄子在线接单