1.redis客户端 jedis和lettuce
网上大部分是基于jedis的分布式锁 jedis是多线程下不是线程安全的 lettuce是基于netty线程安全性能高,
springboot2.0都采用lettuce,所以分布式锁最好基于lettuce
2.分布式锁原理
采用原子操作如下:
如果不存在key就设置这个key 存在这个key就阻塞住等待锁key自动或手动释放
3.多线程或多进程下存在多问题
当同步代码块阻塞时间过长,导致代码还没执行完,但是此时超过了持有锁的超时时间(key的过期时间)锁会自动被释放,之后同步代码块执行完毕后又会去执行释放锁的操作(类似unlock)。在锁超时之后,未手动释放锁之前如果其他进程重新设了一把锁,当前进程之后的手动释放又会把其他进程设置的新锁释放掉,所以在手动释放锁之前一定要核对value的值是否和设置锁的时候一致的,这样就不会把其他进程的锁给释放掉。
置key时 value一定要随机而且唯一(就像uuid)原因如上;
4.贴代码
@Component
@Slf4j
public class RedisLock {
@Autowired
private RedisTemplate redisTemplate;
/**
* 存储当前线程 设置锁对应的value 用来释放锁时进行校验
*/
private final ThreadLocal<String> lockValue = new ThreadLocal<>();
private final String keyLock = "lock";
/**
* 获取锁 (带超时时间)
*
* @param timeout
* @param expireTime
* @return
* @throws InterruptedException
*/
public boolean tryLockWithTimeout(Integer timeout, Integer expireTime) {
String value = UUID.randomUUID().toString();
//获取未来过期时间点
long invalidTime = System.currentTimeMillis() + timeout * 1000;
boolean flag = false;
while (System.currentTimeMillis() < invalidTime) {
flag = tryLock(keyLock, value, expireTime);
if (flag) {
break;
} else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
log.error(e.toString());
}
}
}
if (false == flag) {
log.error("竞争锁失败");
}
return flag;
}
/**
* 获取锁 (一直抢到为止) 阻塞锁
*
* @param expireTime 锁失效时间
* @return
*/
public boolean blockLock(Long expireTime) throws InterruptedException {
String value = UUID.randomUUID(