跳转到内容

Spring悲观锁

来自代码酷

Spring悲观锁[编辑 | 编辑源代码]

Spring悲观锁是Spring ORM整合中用于处理并发数据访问冲突的重要机制。悲观锁假设并发事务很可能发生冲突,因此在数据访问时直接锁定资源,阻止其他事务的干扰。这种策略适用于高并发写操作场景,能有效避免脏读、不可重复读等问题。

核心概念[编辑 | 编辑源代码]

悲观锁定义[编辑 | 编辑源代码]

悲观锁(Pessimistic Locking)是一种并发控制策略,其核心思想是:

  • 事务在访问数据前先获取锁
  • 持有锁期间阻止其他事务修改数据
  • 适用于写多读少的高竞争环境

数学表达为: PessimisticLock(Ti)=TjTi,Block(Tj) until Commit(Ti)

与乐观锁对比[编辑 | 编辑源代码]

特性 悲观锁 乐观锁
并发假设 高冲突概率 低冲突概率
实现方式 数据库锁机制 版本号/时间戳
性能开销 较高(阻塞等待) 较低(无阻塞)
适用场景 银行交易、库存扣减 评论系统、日志记录

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);

实际案例[编辑 | 编辑源代码]

电商库存扣减[编辑 | 编辑源代码]

场景流程:

sequenceDiagram participant Client participant Service participant DB Client->>Service: 下单请求(商品A) Service->>DB: 获取PESSIMISTIC_WRITE锁 DB-->>Service: 返回锁定商品数据 Service->>DB: 校验并扣减库存 DB-->>Service: 操作成功 Service->>Client: 返回订单确认

实现代码:

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. 避免跨服务的分布式悲观锁

常见问题[编辑 | 编辑源代码]

死锁处理[编辑 | 编辑源代码]

典型场景:

graph LR T1[事务1: 锁A请求B] -->|等待| T2[事务2: 锁B请求A] T2 -->|等待| T1

解决方案:

  • 使用`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悲观锁为高竞争数据访问提供了可靠的并发控制方案。开发者应当:

  • 根据业务特点选择适当的锁类型
  • 注意事务边界和锁作用域
  • 监控锁竞争情况并优化
  • 在一致性和性能之间取得平衡

合理使用悲观锁能有效解决电商、金融等领域的并发难题,但需警惕过度使用导致的系统瓶颈。