Spring悲观锁
外观
Spring悲观锁[编辑 | 编辑源代码]
Spring悲观锁是Spring ORM整合中用于处理并发数据访问冲突的重要机制。悲观锁假设并发事务很可能发生冲突,因此在数据访问时直接锁定资源,阻止其他事务的干扰。这种策略适用于高并发写操作场景,能有效避免脏读、不可重复读等问题。
核心概念[编辑 | 编辑源代码]
悲观锁定义[编辑 | 编辑源代码]
悲观锁(Pessimistic Locking)是一种并发控制策略,其核心思想是:
- 事务在访问数据前先获取锁
- 持有锁期间阻止其他事务修改数据
- 适用于写多读少的高竞争环境
数学表达为:
与乐观锁对比[编辑 | 编辑源代码]
特性 | 悲观锁 | 乐观锁 |
---|---|---|
并发假设 | 高冲突概率 | 低冲突概率 |
实现方式 | 数据库锁机制 | 版本号/时间戳 |
性能开销 | 较高(阻塞等待) | 较低(无阻塞) |
适用场景 | 银行交易、库存扣减 | 评论系统、日志记录 |
Spring中的实现[编辑 | 编辑源代码]
配置基础[编辑 | 编辑源代码]
需确保Spring配置了事务管理器和JPA/Hibernate支持:
<!-- persistence.xml 示例 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaProperties">
<props>
<prop key="javax.persistence.lock.scope">PESSIMISTIC</prop>
</props>
</property>
</bean>
编程实现[编辑 | 编辑源代码]
通过JPA的`LockModeType`实现悲观锁:
@Repository
public class ProductRepository {
@PersistenceContext
private EntityManager entityManager;
// 悲观写锁(阻塞其他读写)
public Product findWithPessimisticWriteLock(Long id) {
return entityManager.find(Product.class, id,
LockModeType.PESSIMISTIC_WRITE);
}
// 悲观读锁(阻塞其他写操作)
public Product findWithPessimisticReadLock(Long id) {
return entityManager.find(Product.class, id,
LockModeType.PESSIMISTIC_READ);
}
}
事务边界[编辑 | 编辑源代码]
必须确保锁在事务内生效:
@Service
@Transactional
public class InventoryService {
@Autowired
private ProductRepository repository;
public void updateStock(Long productId, int quantity) {
Product product = repository.findWithPessimisticWriteLock(productId);
product.setStock(product.getStock() - quantity);
// 事务提交时自动释放锁
}
}
锁类型详解[编辑 | 编辑源代码]
PESSIMISTIC_WRITE[编辑 | 编辑源代码]
- 排他锁(X锁)
- 阻塞其他事务的读写操作
- 对应SQL:`SELECT ... FOR UPDATE`
PESSIMISTIC_READ[编辑 | 编辑源代码]
- 共享锁(S锁)
- 允许并发读但阻塞写操作
- 对应SQL:`SELECT ... LOCK IN SHARE MODE`(MySQL)
锁超时控制[编辑 | 编辑源代码]
可通过`javax.persistence.lock.timeout`设置(单位毫秒):
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.lock.timeout", 5000);
entityManager.find(Product.class, id,
LockModeType.PESSIMISTIC_WRITE, properties);
实际案例[编辑 | 编辑源代码]
电商库存扣减[编辑 | 编辑源代码]
场景流程:
实现代码:
public OrderResult placeOrder(Long productId, int quantity) {
try {
Product product = productRepository.findWithPessimisticWriteLock(productId);
if (product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
return OrderResult.success();
}
return OrderResult.fail("库存不足");
} catch (LockTimeoutException e) {
return OrderResult.fail("系统繁忙,请重试");
}
}
性能考量[编辑 | 编辑源代码]
优点[编辑 | 编辑源代码]
- 保证强一致性
- 避免乐观锁的重试开销
- 简单直观的实现方式
缺点[编辑 | 编辑源代码]
- 可能引起死锁
- 降低系统吞吐量
- 长时间持有锁会导致连接池耗尽
最佳实践[编辑 | 编辑源代码]
1. 尽量缩小锁的粒度(行级锁优于表级锁) 2. 控制事务执行时间 3. 设置合理的锁超时时间 4. 避免跨服务的分布式悲观锁
常见问题[编辑 | 编辑源代码]
死锁处理[编辑 | 编辑源代码]
典型场景:
解决方案:
- 使用`tryLock`而非阻塞等待
- 统一资源获取顺序
- 设置死锁检测超时
锁升级问题[编辑 | 编辑源代码]
当同时使用悲观读锁和写锁时可能引发:
// 危险操作!
Product p1 = entityManager.find(Product.class, id,
LockModeType.PESSIMISTIC_READ);
// 尝试升级为写锁
entityManager.lock(p1, LockModeType.PESSIMISTIC_WRITE);
应直接使用写锁避免此情况。
进阶技巧[编辑 | 编辑源代码]
自定义锁实现[编辑 | 编辑源代码]
扩展`AbstractOwnableSynchronizer`实现定制锁逻辑:
public class InventoryLock extends AbstractOwnableSynchronizer {
public boolean tryLock(long timeout, TimeUnit unit) {
// 自定义实现...
}
}
与Spring Cache集成[编辑 | 编辑源代码]
结合`@Cacheable`实现缓存层锁定:
@Cacheable(value = "products", key = "#id")
@Lock(LockModeType.PESSIMISTIC_WRITE)
public Product getProduct(Long id) {
// ...
}
总结[编辑 | 编辑源代码]
Spring悲观锁为高竞争数据访问提供了可靠的并发控制方案。开发者应当:
- 根据业务特点选择适当的锁类型
- 注意事务边界和锁作用域
- 监控锁竞争情况并优化
- 在一致性和性能之间取得平衡
合理使用悲观锁能有效解决电商、金融等领域的并发难题,但需警惕过度使用导致的系统瓶颈。