悲观锁和乐观锁如何区分?(一)
【摘要】 悲观锁和乐观锁是工作中两种常见的并发控制机制,它们主要用于处理多线程或多进程环境中的数据访问冲突问题,在数据库系统、分布式系统和多线程编程中都有广泛应用。这篇文章,我们来分析悲观锁和乐观锁的原理以及使用场景。悲观锁定义悲观锁(Pessimistic Lock),顾名思义,就是持有悲观状态,假设冲突会频繁发生的锁机制。每次数据访问时,都会先加锁,直到操作完成后才释放锁,这样可以确保在锁持有期间...
悲观锁和乐观锁是工作中两种常见的并发控制机制,它们主要用于处理多线程或多进程环境中的数据访问冲突问题,在数据库系统、分布式系统和多线程编程中都有广泛应用。这篇文章,我们来分析悲观锁和乐观锁的原理以及使用场景。
悲观锁
定义
悲观锁(Pessimistic Lock),顾名思义,就是持有悲观状态,假设冲突会频繁发生的锁机制。每次数据访问时,都会先加锁,直到操作完成后才释放锁,这样可以确保在锁持有期间,其他线程无法访问这段数据,从而避免了并发冲突。
悲观锁的实现通常有以下两种方式:
-
数据库:在数据库中,悲观锁通常通过SQL语句实现,例如 SELECT ... FOR UPDATE
。 -
编程语言:在编程语言中,悲观锁可以使用互斥锁(Mutex)或同步块(Synchronized Block)来实现。
应用场景
适用于对数据并发冲突非常敏感的场景,例如银行转账操作、库存扣减等需要严格数据一致性的操作。
优缺点
-
优点:可以完全避免并发冲突,保证数据的一致性和完整性。 -
缺点:由于每次访问数据都需要加锁和解锁,会导致性能开销较大,特别是在并发量高的情况下,容易造成锁竞争和死锁问题。
示例
下面我们用 Java + MySQL 展示了一个悲观锁的具体实现。
假设有一个银行账户表(Account
),包含账户 ID和余额两个字段,我们希望在更新账户余额时使用悲观锁,以确保数据的一致性。
整个运行流程分为以下4个步骤:
-
获取账户信息并锁定记录( SELECT ... FOR UPDATE
)。 -
计算新的余额。 -
更新账户信息。 -
由于使用了 @Transactional
注解,整个方法执行在一个事务中,确保在事务提交之前,锁定的记录不会被其他事务修改。
数据库表结构
CREATE TABLE Account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2) NOT NULL
);
Java实现示例
1. Account类
public class Account {
private int id;
private BigDecimal balance;
// Getters and Setters
}
2. AccountMapper接口
public interface AccountMapper {
Account getAccountByIdForUpdate(int id);
void updateAccount(Account account);
}
3. AccountMapper的SQL实现
<mapper namespace="com.example.AccountMapper">
<select id="getAccountByIdForUpdate" resultType="com.example.Account">
SELECT id, balance FROM Account WHERE id = #{id} FOR UPDATE
</select>
<update id="updateAccount">
UPDATE Account
SET balance = #{balance}
WHERE id = #{id}
</update>
</mapper>
4. AccountService类
import org.springframework.transaction.annotation.Transactional;
publicclass 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) {
thrownew RuntimeException("Account not found");
}
// 更新余额
account.setBalance(account.getBalance().add(amount));
// 更新账户信息
accountMapper.updateAccount(account);
}
}
示例说明:
-
Account类:包含账户ID和余额的Java类。 -
AccountMapper接口:定义了获取账户信息(带锁定)和更新账户信息的方法。 -
AccountMapper的SQL实现:使用MyBatis或其他ORM框架,定义了SQL查询和更新语句。注意在查询语句中使用 FOR UPDATE
来锁定记录。 -
AccountService类:业务逻辑类,在更新账户余额时,先获取当前账户信息并锁定记录,然后更新余额并提交更新。
这种机制确保了在操作完成之前,其他线程无法修改锁定的记录,从而实现了悲观锁的并发控制。
注意事项
-
事务管理:使用悲观锁时,需要确保在事务提交之前锁不会被释放,因此必须在事务中使用。 -
死锁风险:悲观锁可能会导致死锁,需要特别注意死锁检测和处理。 -
性能影响:由于每次操作都需要加锁和解锁,性能可能会受到影响,特别是在高并发情况下。
通过了解悲观锁的具体实现,可以在需要严格数据一致性的场景中有效地避免并发冲突。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)