Long timeout = 3000L;//获取锁超时时间
Map lockCacheMap = new ConcurrentHashMap<>();//本地缓存锁
public boolean tryLock() {
//获取当前线程
Thread cuThread = Thread.currentThread();
//先从本地缓存中获取锁实例
RedisLockInfo redisLockInfo = lockCacheMap.get(cuThread);
if (redisLockInfo != null && redisLockInfo.isState()) {
// 这把锁可重入次数+1
redisLockInfo.reentry++;
log.info("<您在之前已经获取过锁,锁直接可重入>");
return true;
}
Long startTime = System.currentTimeMillis();
Long expire = 30000L; //过期时间
String lockId = UUID.randomUUID().toString(); //锁唯一id
for (; ; ) {
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, lockId, expire, TimeUnit.SECONDS);
if (lock) {
log.info("<获取锁成功>");
RedisLockInfo newRedisLockInfo = new RedisLockInfo(lockId, cuThread, expire);
lockCacheMap.put(cuThread, newRedisLockInfo);
// 开启锁续命线程
new Thread(new LifeExtensionThread(newRedisLockInfo, 10000)).start();
return true;
}
// 获取锁失败, 控制再次获取的超时时间
Long endTime = System.currentTimeMillis();
if (endTime - startTime > timeout) {
log.info("<重试的时间已经过了,不能够在继续重试啦>");
return false;
}
}
}
锁实例
public RedisLockInfo(String lockId, Thread lockThread, Long expire, Long reentry) {
state = true;//锁状态, true-获取锁
this.lockId = lockId; //锁的id
this.lockThread = lockThread; //持有锁的线程
this.expire = expire; //过期时间
lifeCount = new AtomicInteger(0); // 续期次数, 原子操作
this.reentry = reentry; //重试次数
}
说明:
获取锁后, 记录锁实例信息, 并缓存到本地
控制获取锁的时间, 避免过去消耗cpu资源
public boolean releaseLock() {
//本地缓存查找当前线程锁实例
RedisLockInfo redisLockInfo = lockCacheMap.get(Thread.currentThread());
if (redisLockInfo == null) {
return false;
}
String redisLockId = stringRedisTemplate.opsForValue().get(redisLockKey);
if (StringUtils.isEmpty(redisLockId)) {
log.error("该key已经过期或者不存在");
return false;
}
//线程删除各自的锁
String lockId = redisLockInfo.getLockId();
if (!lockId.equals(redisLockId)) {
log.error("不是当前我自己线程调用删除key");
return false;
}
return stringRedisTemplate.delete(redisLockKey);
}
说明:
在释放锁的时候, 会把当前线程lockid和redis中的redisLockId对比, 是为了防止误删
假设a线程业务时长20s, 锁时长10s, 那么在第10s的时候, 锁会被释放;
这时b线程获取锁, 当第20s时候, a线程执行完业务, 开始释放锁, 实际上释放的是b线程的锁
这时c线程获取锁...
class LifeExtensionThread implements Runnable {
private RedisLockInfo redisLockInfo;
private int interval;
public LifeExtensionThread(RedisLockInfo redisLockInfo, int interval) {
this.redisLockInfo = redisLockInfo;
this.interval = interval;
}
@Override
public void run() {
while (true) {
try {
// 每隔一段时间实现续命
Thread.sleep(interval);
// 锁实例为空, 不再续命
if (redisLockInfo == null) {
return;
}
//当前线程执行完毕, 锁已释放
Thread lockThread = redisLockInfo.getLockThread();
if (redisLockInfo.isState() && lockThread.isInterrupted()) {
log.info(">>当前获取到锁的线程,已经执行完毕啦, 锁已经释放啦。<<");
return;
}
//锁 对应线程还在一直使用 没有释放
Integer lifeCount = redisLockInfo.getLifeCount();
// 续命次数如果超过3次以上停止
if (lifeCount > 3) {
log.info(">>续命的次数已经达到了次数,开始主动释放锁<<");
// 1.回滚当前事务
//2.释放锁
stringRedisTemplate.delete(redisLockKey);
//3.该拿到锁的线程应该主动停止掉
lockThread.interrupt();
return;
}
// 延长过期key的时间, 结合lua脚本续命
stringRedisTemplate.expire(redisLockKey, redisLockInfo.getExpire(),TimeUnit.SECONDS);
} catch (Exception e) {
log.error(">>e:{e}<<", e);
}
}
}
}
说明:
开启一个线程死循环, 只要当前线程锁没有被释放, 就续命
可设置续命频率(默认10s), 续命时长(默认30s)和续命次数
锁一旦被释放, 就停止续命