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

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

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

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

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

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

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

推荐文章

api接口怎么对接
2024-11-19 09:42:47 +0800 CST
html夫妻约定
2024-11-19 01:24:21 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
Vue3中如何处理权限控制?
2024-11-18 05:36:30 +0800 CST
mysql 计算附近的人
2024-11-18 13:51:11 +0800 CST
维护网站维护费一年多少钱?
2024-11-19 08:05:52 +0800 CST
html一个包含iPhoneX和MacBook模拟器
2024-11-19 08:03:47 +0800 CST
2025,重新认识 HTML!
2025-02-07 14:40:00 +0800 CST
mysql int bigint 自增索引范围
2024-11-18 07:29:12 +0800 CST
基于Flask实现后台权限管理系统
2024-11-19 09:53:09 +0800 CST
禁止调试前端页面代码
2024-11-19 02:17:33 +0800 CST
JavaScript 异步编程入门
2024-11-19 07:07:43 +0800 CST
底部导航栏
2024-11-19 01:12:32 +0800 CST
Vue3中如何处理状态管理?
2024-11-17 07:13:45 +0800 CST
`Blob` 与 `File` 的关系
2025-05-11 23:45:58 +0800 CST
Vue3中的v-for指令有什么新特性?
2024-11-18 12:34:09 +0800 CST
LLM驱动的强大网络爬虫工具
2024-11-19 07:37:07 +0800 CST
Vue 3 是如何实现更好的性能的?
2024-11-19 09:06:25 +0800 CST
平面设计常用尺寸
2024-11-19 02:20:22 +0800 CST
JavaScript设计模式:组合模式
2024-11-18 11:14:46 +0800 CST
Golang Sync.Once 使用与原理
2024-11-17 03:53:42 +0800 CST
程序员茄子在线接单