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