第一步 redis分布式锁工具类
package com.example.demoone.redis;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/**
* @author GXY
* @Package com.an.redis.util
* @date 2020/5/25 11:02
* @Copyright © 2020-2021 新流通
*/
@Component
public class RedisTemplateHandler {
@Resource
private StringRedisTemplate stringRedisTemplate;
public static boolean COMMON_ERROR_KEY = Boolean.FALSE;
/**
* 插入分布式Job Redis锁
* @param key 锁的key
* @param val 锁的value
* @param exprire 过期时间(秒)
* @return
*/
public boolean redisSetNX(String key,String val,long exprire){
Boolean result = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
return connection.setNX(key.getBytes(), val.getBytes());
});
// TODO-GXY: 2020/5/25 17:10 上锁与设置过期时间为两步,非原子操作,这里不够严谨。借助下面的redisCheckNX弥补;后期也可以优化成一步操作
if (result){
stringRedisTemplate.expire(key,exprire, TimeUnit.SECONDS);
}
return result ;
}
/**
* 删除分布式Job Redis锁
* @param key
* @return
*/
public boolean redisDelNX(String key){
Boolean delete = stringRedisTemplate.delete(key);
return delete;
}
/**
* 检查分布式Job Redis锁
* @param key
* @param lockSeconds 给锁上的过期时长(秒)
* @return
*/
public boolean redisCheckNX(String key,int lockSeconds){
//根据key获取过期时间
Long expire = stringRedisTemplate.getExpire(key);
String s = stringRedisTemplate.opsForValue().get(key);
long time = 0;
if (StringUtils.isNotBlank(s)){
time = Calendar.getInstance().getTimeInMillis() - Long.valueOf(s).longValue();
}else {
//key不存在说明锁过期
COMMON_ERROR_KEY = false;
return COMMON_ERROR_KEY;
}
//超时过期
if (expire <= 0 || time > lockSeconds * 1000L){
//删除key,释放锁
System.out.println("出现死锁,手动释放锁");
redisDelNX(key);
COMMON_ERROR_KEY = false;
return COMMON_ERROR_KEY;
}
COMMON_ERROR_KEY = true;
return COMMON_ERROR_KEY;
}
}
第二步 简单测试类
package com.example.demoone.abc;
import com.example.demoone.redis.RedisLock;
import com.example.demoone.redis.RedisTemplateHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.Calendar;
/**
* @author gaoxi
* @title: test
* @projectName demoone
* @description: TODO
* @date 2020/03/26 12:28
*/
@Controller
public class test {
@Autowired
private RedisTemplateHandler redisTemplateHandler;
@RequestMapping("/redis")
@ResponseBody
public void redisLock(){
try {
boolean lock = redisTemplateHandler.redisSetNX("LOCK", String.valueOf(Calendar.getInstance().getTimeInMillis()), 7L);
//成功获取锁
if (lock){
System.out.println("成功获取锁的线程是:");
System.out.println("--"+Thread.currentThread().getName());
}else {
while (true){
System.out.println(Thread.currentThread().getName()+"未获得锁");
Thread.sleep(3000);
boolean lock1 = redisTemplateHandler.redisCheckNX("LOCK", 7);
//执行锁失效,造成死锁
if (!lock1){
//其实这里不用再次执行redisDelNX方法,redisCheckNX方法中已经执行过了,以防万一
redisTemplateHandler.redisDelNX("LOCK"); // 释放执行锁
break;
}
}
redisLock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
第三步 利用Jmeter 模拟多线程并发抢锁
测试结果:
为了方便截图,我是用5个线程测试的,这里只贴出部分结果,首先--http-nio-8082-exec-10线程成功抢到锁,其他线程阻塞,没隔3秒重试验证锁是否存在,如果锁没有过期,则继续等待;如果锁已过期不存在,则开始抢锁。当出现死锁时(我是测试用来制造的死锁),手动释放锁。