废话不说,先上代码:
//controller层
@GetMapping("/testLock")
public Result testLock(){
testService.testRedisLock(lockName);
return Result.ok();
}
//service层
@Autowired
private RedisTemplate redisTemplate;
public void testRedisLock(String lockName) {
//获取锁
//加上uuid防止误删除锁
String uuid = System.currentTimeMillis() + UUID.randomUUID().toString().replaceAll("-", "");
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockName, uuid, 10, TimeUnit.SECONDS);
//如果获取到锁执行步骤
//最后释放锁
if (lock) {
String num = redisTemplate.opsForValue().get("num").toString();
if (!StringUtils.isEmpty(num)) {
int i = Integer.parseInt(num);
redisTemplate.opsForValue().set("num", String.valueOf(++i));
} else {
return;
}
//在极端情况下仍然会误删除锁(线程A将下面的if语句块执行了一半,锁时间到,线程B进入,但线程走完if依据把锁删了)
/* if (redisTemplate.opsForValue().get(lockName).toString().equals(uuid)){
redisTemplate.delete("lock");
}*/
//因此使用lua脚本的方式来防止误删除
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
"then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
DefaultRedisScript defaultRedisScript = new DefaultRedisScript();
defaultRedisScript.setScriptText(script);
defaultRedisScript.setResultType(Long.class);
redisTemplate.execute(defaultRedisScript, Arrays.asList("lock"), uuid);
} else {
//如果没有获取到锁
//重试
try {
Thread.sleep(100);
testRedisLock();;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
值得注意的点:
- 要在锁上加上过期时间,防止线程卡死,长时间占用;
- 要给锁的Value设置uuid,用来作解锁判断,同时也可防止误删;
- 锁的key值可以作为变化参数,例如在集群管理中,key可以设定为主机IP值;