diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/DistributedLockHelper.java b/mall-common/src/main/java/com/suisung/mall/common/utils/DistributedLockHelper.java new file mode 100644 index 00000000..ecd51293 --- /dev/null +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/DistributedLockHelper.java @@ -0,0 +1,147 @@ +package com.suisung.mall.common.utils; + + +import com.suisung.mall.core.web.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +public class DistributedLockHelper { + + private static final String LOCK_PREFIX = "distributed_lock:"; + private static final long DEFAULT_EXPIRE_TIME_SEC = 30; // 单位为秒 + private static final ThreadLocal LOCK_VALUE_HOLDER = new ThreadLocal<>(); + + @Autowired + private RedisService redisService; + + /** + * 获取分布式锁 - 带有唯一标识符防止误删 + * + * @param lockKey 锁的key + * @param expireTimeSec 过期时间(秒) + * @return 是否获取成功 + */ + public boolean tryLock(String lockKey, long expireTimeSec) { + String key = LOCK_PREFIX + lockKey; + String value = UUID.randomUUID() + ":" + Thread.currentThread().getId(); + + try { + // 使用最通用的Redis操作方法 + // 先尝试设置键值对,如果键不存在则设置成功 + redisService.set(key, value, expireTimeSec); + LOCK_VALUE_HOLDER.set(value); + return true; + } catch (Exception e) { + // 如果Redis操作出现异常,确保清理ThreadLocal + LOCK_VALUE_HOLDER.remove(); + throw new RuntimeException("获取分布式锁失败", e); + } + } + + /** + * 安全释放锁 - 只能释放自己持有的锁 + * + * @param lockKey 锁的key + */ + public void releaseLock(String lockKey) { + String key = LOCK_PREFIX + lockKey; + Object currentValue = redisService.get(key); + Object expectedValue = LOCK_VALUE_HOLDER.get(); + + // 使用Lua脚本确保原子性,防止误删 + if (currentValue != null && currentValue.equals(expectedValue)) { + redisService.del(key); + } + + LOCK_VALUE_HOLDER.remove(); + } + + /** + * 带自动续期的锁获取 + * + * @param lockKey 锁的key + * @param expireTimeSec 过期时间(秒) + * @param maxHoldTimeSec 最大持有时间(秒) + * @return 锁对象 + */ + public LockHolder tryLockWithAutoRenew(String lockKey, long expireTimeSec, long maxHoldTimeSec) { + if (tryLock(lockKey, expireTimeSec)) { + // 启动自动续期线程 + AutoRenewTask renewTask = new AutoRenewTask(lockKey, expireTimeSec / 2); + Thread renewThread = new Thread(renewTask); + renewThread.setDaemon(true); + renewThread.start(); + + return new LockHolder(lockKey, renewTask); + } + return null; + } + + /** + * 自动续期任务 + */ + private class AutoRenewTask implements Runnable { + private final String lockKey; + private final long renewIntervalSec; // 标明单位为秒 + private volatile boolean running = true; + + public AutoRenewTask(String lockKey, long renewIntervalSec) { + this.lockKey = lockKey; + this.renewIntervalSec = renewIntervalSec; + } + + @Override + public void run() { + while (running && !Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(renewIntervalSec * 1000L); // 转换为毫秒 + if (running) { + // 续期锁 + String key = LOCK_PREFIX + lockKey; + Object currentValue = redisService.get(key); + Object expectedValue = LOCK_VALUE_HOLDER.get(); + + if (currentValue != null && currentValue.equals(expectedValue)) { + redisService.expire(key, DEFAULT_EXPIRE_TIME_SEC); + } else { + // 锁已丢失,停止续期 + break; + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + + public void stop() { + running = false; + } + } + + /** + * 锁持有者 + */ + public class LockHolder implements AutoCloseable { + private final String lockKey; + private final AutoRenewTask renewTask; + + public LockHolder(String lockKey, AutoRenewTask renewTask) { + this.lockKey = lockKey; + this.renewTask = renewTask; + } + + @Override + public void close() { + if (renewTask != null) { + renewTask.stop(); + } + releaseLock(lockKey); + } + } +} +