当前位置:首页
> .html
导读:我们设置key的时候,将value设置为一个随机值r,并且存在当前线程ThreadLocal。当释放锁,也就是删除key的时候,不是直接删除,而是先判断该key对应的value是否...
我们设置key的时候,将value设置为一个随机值r,并且存在当前线程ThreadLocal。当释放锁,也就是删除key的时候,不是直接删除,而是先判断该key对应的value是否等于先前存在当前线程的随机值,只有当前当前线程持有锁,才删除该key,由于每个客户端产生的随机值是不一样的,这样一来就不会误释放别的客户端申请的锁了
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private static OnecachePlugin oneCache;
static {
oneCache = SpringUtil.getBean(OnecachePlugin.class);
}
/**
* 基础有效时间
*/
private static final int BASE_VAILD_TIME = 10;
/**
* 锁的基本等待时间10s
*/
private static final int BASE_WAIT_TIME = 4;
/**
* 随机数
*/
private static Random random = new Random();
private static ThreadLocal<Set<String>> threadLocal = new ThreadLocal<>();
private static void currentThreadSleep() throws InterruptedException {
Thread.sleep((long) (200), random.nextInt(5000));
}
public static boolean easyLock(String key) throws Exception {
return easyLock(key, BASE_WAIT_TIME, BASE_VAILD_TIME);
}
//加锁
public static boolean easyLock(String key, Integer waitTime, Integer expireTime) throws Exception {
if (ObjectUtil.hasEmpty(waitTime)) {
waitTime = BASE_WAIT_TIME;
}
if (ObjectUtil.hasEmpty(expireTime)) {
expireTime = BASE_VAILD_TIME;
}
Long signTime = System.nanoTime();
Long holdTime = TimeUnit.NANOSECONDS.convert(waitTime, TimeUnit.SECONDS);
String state = UUIDGenerator.getUUID();
while ((System.nanoTime() - signTime) < holdTime) {
//从redis获取key 如果不存在,则将key存入redis
RString rs = oneCache.getRString(key);
//原子性加锁
if (rs.setnx(state, Time.seconds(expireTime))) {
logger.info(Thread.currentThread().getId()+" 获取锁成功! key = "+key);
if(ObjectUtil.hasEmpty(threadLocal.get())){
threadLocal.set(Sets.newHashSet());
}
threadLocal.get().add(state);
return true;
} else {
try {
currentThreadSleep();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
throw new Exception( "系统正忙,稍后重试");
}
/**
* redis简单分布式锁,业务执行完毕之后必须try finally 调用释放锁方法easyUnLock
*
* @param key 锁
*/
public static void easyUnLock(String key) {
logger.info(Thread.currentThread().getId()+" 准备解锁! key = "+key);
//是否是当前线程持有锁,以免释放其他线程加的锁
if (isHoldEasyLock(key)) {
logger.info(Thread.currentThread().getId()+" 开始解锁! key = "+key);
try {
//从redis删除key
RString rs = oneCache.getRString(key);
String state = rs.get();
rs.delete();
threadLocal.get().remove(state);
logger.info(Thread.currentThread().getId()+" 解锁成功! key = "+key);
} catch (Exception e) {
logger.info("RedisLockUtils easyLock解锁异常 ->" + e);
}
}
}
/**
* redis简单分布式锁,判断线程是否持有锁
*/
public static boolean isHoldEasyLock(String key) {
if (ObjectUtil.hasEmpty(threadLocal.get())) {
return false;
}
if (threadLocal.get().contains(oneCache.getRString(key).get())) {
return true;
} else {
return false;
}
}
}
实际上,这样还是有一点问题,释放锁不是原子性,很有可能在查询完,redis也刚过期,再删除就把别的线程的锁释放了。

image.png
对以上问题解决办法,就是使用lua脚本,参考
https://www.jianshu.com/p/0e5d592197c1。
至此,还没有完,就是过期时间的的问题,如果高并发下,某个线程被阻塞,导致超时,那么redis过期了,就导致并发问题了,如果说过期时间设置太长,如果服务重启了,那么key就释放不了了,
因此,稳妥一点的解决办法,就是锁续命。
就是在加锁后,异步起一个线程,每隔几秒去判断一下,redis锁是否还在或者过期,给重新设置锁的过期时间,这样会完美解决上述问题。
具体实现有现成框架redisson
我们项目并发量一般,所以普通加锁就能满足
发表评论: