悲观锁和乐观锁如何区分?(二)
【摘要】 乐观锁定义乐观锁(Optimistic Lock)是一种假设冲突不会频繁发生的锁机制。每次数据访问时,不会加锁,而是在更新数据时检查是否有其他线程修改过数据。如果检测到冲突(数据被其他线程修改过),则重试操作或报错。乐观锁通常实现方式有以下两种:版本号机制:每次读取数据时,读取一个版本号,更新数据时,检查版本号是否变化,如果没有变化,则更新成功,否则重试。时间戳机制:类似版本号机制,通过时间...
乐观锁
定义
乐观锁(Optimistic Lock)是一种假设冲突不会频繁发生的锁机制。每次数据访问时,不会加锁,而是在更新数据时检查是否有其他线程修改过数据。如果检测到冲突(数据被其他线程修改过),则重试操作或报错。
乐观锁通常实现方式有以下两种:
-
版本号机制:每次读取数据时,读取一个版本号,更新数据时,检查版本号是否变化,如果没有变化,则更新成功,否则重试。 -
时间戳机制:类似版本号机制,通过时间戳来检测数据是否被修改。
应用场景
适用于读多写少的场景,例如用户评论系统、社交媒体点赞等,这些场景下并发冲突概率较低。
优缺点
-
优点:避免了频繁的锁操作,性能较好,适合读多写少的场景。 -
缺点:在高并发写操作的场景下,重试可能会频繁发生,导致性能下降。
示例
乐观锁的实现通常涉及到版本号(或时间戳)机制,以便在更新数据时检测是否发生了并发修改。我们还是用上面的示例,展示了如何在 Java中使用乐观锁进行并发控制。
假设有一个银行账户表(Account
),包含账户ID、余额和版本号三个字段,现在希望在更新账户余额时使用乐观锁,以确保数据的一致性。
整个运行流程总结为下面 3个步骤:
-
获取账户信息,包括当前的版本号。 -
计算新的余额,并增加版本号。 -
尝试更新账户信息,如果版本号匹配则更新成功,否则更新失败并抛出异常。
数据库表结构
CREATE TABLE Account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2) NOT NULL,
version INT NOT NULL
);
Java实现示例
1. Account类
public class Account {
private int id;
private BigDecimal balance;
private int version;
// Getters and Setters
}
2. AccountMapper接口
public interface AccountMapper {
Account getAccountById(int id);
int updateAccount(Account account);
}
3. AccountMapper的SQL实现
<mapper namespace="com.example.AccountMapper">
<select id="getAccountById" resultType="com.example.Account">
SELECT id, balance, version FROM Account WHERE id = #{id}
</select>
<update id="updateAccount">
UPDATE Account
SET balance = #{balance}, version = #{version}
WHERE id = #{id} AND version = #{oldVersion}
</update>
</mapper>
4. 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) {
thrownew 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) {
// 更新失败,可能是由于并发修改导致的版本号不匹配
thrownew OptimisticLockException("Update failed due to concurrent modification");
}
}
}
示例说明:
-
Account类:包含账户ID、余额和版本号的Java类。 -
AccountMapper接口:定义了获取账户信息和更新账户信息的方法。 -
AccountMapper的SQL实现:使用MyBatis或其他ORM框架,定义了SQL查询和更新语句。注意在更新语句中使用了旧版本号来检测并发修改。 -
AccountService类:业务逻辑类,在更新账户余额时,先获取当前账户信息及其版本号,然后尝试更新余额和版本号。如果更新失败,抛出一个 OptimisticLockException
。
区别总结
1. 假设前提:
-
悲观锁假设冲突会频繁发生,需要加锁保护。 -
乐观锁假设冲突不会频繁发生,通过版本号或时间戳来检测冲突。
2.性能:
-
悲观锁性能较低,因为每次操作都需要加锁和解锁。 -
乐观锁性能较高,但在高并发写操作下可能会频繁重试,影响性能。
3.应用场景:
-
悲观锁适用于并发冲突高、数据一致性要求严格的场景。 -
乐观锁适用于并发冲突低、读多写少的场景。
总结
本文我们详细分析了悲观锁和乐观锁的原理、区别、实现方式和应用场景,实际工作中,可以根据具体需求选择合适的并发控制机制,以保证系统的性能和数据一致性。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)