setnx 如果当前有这个key就返回0没有就设置成功返回1
expire给指定key设置过期时间,如果不设置过期时间的话,当执行完一遍的时候,别的线程就永远进不来了.
客户端代码实现,但是这样的话还是会有问题,如果执行完设置key后代码宕掉了,超时时间没设置上,那么这个锁就会一直锁的
时间单位有两种 ex为秒 px 为毫秒 就是下图中ex的位置
nx的位置可以填两种 ex为key存不存在时才能进行操作,存在的时候返回nul xx为key存在时才能进行操作,不存在时返回nil 成功时返回ok
这种方式就是把设置超时时间和设置key合并到一块执行了,就不存在上面那种问题了
实现代码
public void testRedisLockDemo() throws Exception{
/**
* 1.设置key的时候得加上过期时间,因为 如果不设置过期时间的话,当key设置成功,但是下面的delete操作因为异常等原因没有执行
* 这样的话就成了死锁了
* 2.设置uuid的原因是 假设业务逻辑代码执行时间过长,失效时间过了然后当前线程逻辑才执行完,这时别的线程已经拿到了锁,但是当前线程
* 如果把lock删掉的话,这个分布式锁就锁不住了,所以设置一个uuid,只让当前线程删自己的锁.
*
*/
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
if (lock){
//设置成功 说明抢到了锁
//开始时间毫秒值 用来实现续过期时间
long startTime = System.currentTimeMillis();
/**
* 解决办法1:把过期时间设置的特别长
* 解决办法2:自己实现一个续过期时间的操作
*/
Thread thread = new Thread(() -> {
while (true) {
//获取key剩余时间
Long expire = stringRedisTemplate.boundHashOps("lock").getExpire();
//如果剩余时间小于3秒 ,再给当前key设置10秒
if (expire < 3) {
stringRedisTemplate.expire("lock",10 , TimeUnit.SECONDS);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("执行业务代码");
Thread.sleep(5000);
//当执行完成后停掉上面的线程
thread.stop();
//释放锁资源
String lock1 = stringRedisTemplate.opsForValue().get("lock");
if (uuid.equals(lock1)){
//删除时为什么不使用简单删除,而是使用脚本删除
/**
* 假设我逻辑代码执行了九秒,lock的超时时间为10秒,我从redis取数据的时间为1.5秒,前半秒去redis取到了数据
* 并且返回到程序当中,但是现在在返回回来的时候那个lock已经自动删除了,现在新的lock是别的线程创建的
* 但是当前线程执行删除操作把它删掉了,这样就又锁不住了,执行这个脚本的就可以保证这个原子性.
*/
String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end";
//stringRedisTemplate.delete("lock");
stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class), Arrays.asList("lock"),uuid);
}
}else{
//没有抢到,继续循环获取
Thread.sleep(300); //设置下缓冲时间
testRedisLockDemo();
}
}