public class Lock {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
private StringRedisTemplate stringRedisTemplate;
@Value("${lock.ttl: 3000}")
private long lockTTL;
@Value("${lock.wait: 3000}")
private long lockWait;
public Lock(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
public boolean lockWait(String lockKey){
return lockWait(lockKey, lockWait);
}
public boolean lockWait(String lockKey, long lockWaitMillSec) {
log.info("尝试获取锁{}, {}", lockKey, lockWaitMillSec);
long start = System.currentTimeMillis();
while(!lock(lockKey)) {
try {
long current = System.currentTimeMillis();
if(current - start > lockWaitMillSec) {
log.warn("获取锁{}超时{}", lockKey, lockWaitMillSec);
return false;
}
Thread.sleep(100);
} catch (InterruptedException e) {
log.error("等待获得锁异常", e);
}
}
log.info("成功获取到锁{}", lockKey);
return true;
}
boolean lock(String key) {
return lock(key, lockTTL);
}
boolean lock(String key, long lockTTLMillSec) {
String lockKey = lockKey(key);
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
long expireAt = System.currentTimeMillis() + lockTTLMillSec;
boolean acquired;
if(acquired = valueOperations.setIfAbsent(lockKey, String.valueOf(expireAt))) {
threadLocal.set(expireAt);
} else {
String expiredTimeOldValue = valueOperations.get(lockKey);
if (expiredTimeOldValue != null) {
// 锁已超时
if (Long.valueOf(expiredTimeOldValue) < System.currentTimeMillis()) {
String oldValue = valueOperations.getAndSet(lockKey, String.valueOf(expireAt));
// 防止其它线程先获得了锁状态.
if(StringUtils.equals(oldValue, expiredTimeOldValue)) {
log.info("解锁超时锁并重新获得锁 {}:{} => {}", lockKey, expiredTimeOldValue, expireAt);
threadLocal.set(expireAt);
return true;
}
}
}
}
return acquired;
}
public void unlock(String key) {
log.info("释放锁{}", key);
// 可能在未获取到锁的情况下调用
if(threadLocal.get() == null) {
return;
}
String lockKey = lockKey(key);
Object expiredTimeAt = stringRedisTemplate.opsForValue().get(lockKey);
if(expiredTimeAt != null) {
long currentTime = System.currentTimeMillis();
// redis锁未超时, 并且 当前持有锁的任务没超时
if(Long.valueOf(expiredTimeAt.toString()) > currentTime && threadLocal.get() > currentTime) {
stringRedisTemplate.delete(lockKey);
}
}
threadLocal.remove();
}
String lockKey(String key){
return String.format("%s~lock", key);
}
}
工具基于spring框架的StringRedisTemplate,需要spring框架支持,当然也可以自己基于其它方式实现redis操作