加锁
/**
* 尝试为指定的许可证 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();
}
// 若非当前线程持有锁,此处静默跳过(避免抛出异常中断主流程)
}