使用redis服务的redisson架构实现分布式锁
加锁
/*** 尝试为指定的许可证 ID 获取分布式锁。如果锁已被占用,则立即抛出业务异常。** @param licenseId 需要加锁的许可证 ID(即锁名称)* @return true 表示成功获取锁,但请注意:* 锁实际持有时间为 30 秒(或自动续期),* 调用方在业务完成后必须主动释放锁!* @throws BizException 当锁被占用或发生中断异常时抛出*/
public Boolean addLockLicenseId(String licenseId) {// 1. 获取Redisson分布式锁对象:以licenseId作为锁的唯一标识RLock lock = redissonClient.getLock(licenseId);try {// 2. 尝试获取锁(核心逻辑)// - 等待时间(0): 不等待,立即返回获取结果// - 租期时间(30秒): 成功获取锁后,锁自动在30秒后过期释放// - leaseTime = 0 会启用看门狗自动续期(慎用,需确保解锁)boolean locked = lock.tryLock(0, 30, TimeUnit.SECONDS);if (locked) {// 3. 成功获取锁:直接返回true给调用方// 关键:调用方必须在处理完业务后主动调用lock.unlock()释放锁!// 否则30秒后锁会自动过期,但被动等待过期可能导致资源浪费return true;} else {// 4. 锁已被占用:抛出自定义业务异常// ERR_OPT_FAIL建议提示:"操作失败,请稍后重试"throw new BizException(MessageConstant.ERR_OPT_FAIL);}} catch (InterruptedException e) {// 5. 处理中断异常(重要:恢复线程中断状态)// - 避免中断信号被吞没导致线程无法响应停止请求// - 对支持可中断方法的框架(如线程池)非常关键Thread.currentThread().interrupt();throw new BizException("获取锁时被中断,请重试!");}// 注意:没有finally块释放锁!释放锁的职责转交给调用方(成功获取时)
}
解锁
/*** 释放指定许可证ID对应的分布式锁(仅限当前线程持有的锁)* * <p>该方法通过Redisson客户端获取锁对象后,校验当前线程是否持有该锁,* 避免非法释放其他线程持有的锁导致并发安全问题。</p>** @param licenseId 许可证ID(业务唯一标识),用于构造分布式锁的Redis键* * @see RLock#isHeldByCurrentThread() 验证锁持有者的线程安全性* @see RLock#unlock() 释放锁的核心操作*/
public void unLockLicenseId(String licenseId) {// 构造分布式锁的Redis键,格式:"{licenseId}"// 注意:此键必须与加锁时使用的键完全一致,否则无法定位同一把锁[6](@ref)RLock lock = redissonClient.getLock(licenseId);// 校验当前线程是否持有该锁(关键防御性检查)// 1. 防止释放其他线程持有的锁(避免IllegalMonitorStateException)// 2. 避免锁过期自动释放后的无效解锁操作[7](@ref)if (lock.isHeldByCurrentThread()) {// 仅当当前线程持有锁时执行释放操作// 注意:unlock()会递减锁的重入计数器,计数器归零时完全释放锁[6](@ref)lock.unlock();}// 若非当前线程持有锁,此处静默跳过(避免抛出异常中断主流程)
}