多个进程通过使用setnx命令(也就是setIfAbsent方法)向redis中插入同一条key-value键值对,
这句话是核心
1.什么是分布式锁
同步锁和分布式锁的区别:
同步锁:一台机器里的多个线程同时访问
分布式锁:多台机器即多个客户端(一个进程代表一台机器)多个进程同时访问,任何时刻只有一个客户端能持有锁
总结:同步是多个线程,分布式锁是多个进程
2.最简单的一个redis分布式锁
redis的setnx命令 :set if not exists,即当指定的key不存在时,为key设置指定的值,此时setnx命令其实相当于set命令,当指定的key存在时,什么也不做
代码实现:
多个进程通过使用setnx命令(也就是setIfAbsent方法)向redis中插入同一条key-value键值对,插入成功的进程成功加锁,返回true,没有插入成功就没有成功加锁,返回false,待业务代码执行完毕,加锁成功的进程再删除这条key-value键值对就是解锁。
如下面的代码所示:成功执行这条setnx命令的客户端,result就不为空,可以执行业务代码,没有成功执行这条setnx命令的,result就为空,不能执行业务代码
然后删除这条刚刚插入的key-value(这就表示释放锁了),这个时候其他客户端可以执行成功这条setnx插入语句,这样result就不为空,也可以执行业务代码了)
//通过使用setnx命令(也就是setIfAbsent方法)向redis中插入一条key-value键值对的方式拿到锁
Boolean result=stringRedisTemplate.opsForValue().setIfAbsent("job","programmer");
//result不为空,说明这个客户端拿到锁
//result为空,证明这个客户端没有拿到锁
if(result==false)//没有拿到锁
{
return “error”;
}
//从redis中取出stock这个key的value,这时拿到的是字符串,将其转化为整数
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0)//如果此时库存大于0,就扣减库存,stock-1
{
stringTemplate.opsForValue("stock",stock-1+"”);
System.out.println("扣减库存成功");
}
else
{
System.out.println("库存不足,扣减失败");
}
stringRedisTemplate.delete("job");//释放锁,就是删除刚刚插入的key-value键值对
3.改进,防止死锁
上面的方式有可能会造成死锁,比如成功拿到锁的客户端扣减库存的逻辑抛异常了,那就不会执行到释放锁的逻辑,那么该锁是一直没有释放,其他请求时一直无法再成功加锁的,会成为死锁的,其他请求无法再扣减该商品
所以需要给这个插入的键值对设置自动过期时间(这里设置了过期时间为10s,足够执行业代码)
Boolean result=stringRedisTemplate.opsForValue().setIfAbsent("job","programmer");
stringRedisTemplate.expire("job",10,TimeUnit.SECONDS);//设置过期时间为10s
或者直接这么写:两行代码合并
Boolean result=stringRedisTemplate.opsForValue().setIfAbsent("job","programmer",10,TimesUnit.SECONDS);
4.线程A加锁,这把锁却被线程B释放掉了,我们希望别的线程不能释放掉我加的锁
线程1加锁成功,并且设置过期时间为5秒,然后线程开始执行业务逻辑
如果线程1执行时间超过5秒,还没执行完业务逻辑锁的到期时间已经到了
此时线程2成功加上锁了,也设置过期时间,然后一直在执行自己的业务逻辑
此时线程1执行完了业务逻辑,于是去释放锁,此时释放的是线程2加的锁,而不是线程1自己加的锁
解决方法:多个客户端插入的key-value只有key相同,value不同,设置成key=欲扣减库存的商品id,value=线程id
比如说:
线程1设置的是10000011:00012
线程2设置的是10000011:00013
注意setnx命令只要插入的这个key-value键值对key已经存在就会返回false,而不需要key-value完全一样
然后在线程释放锁的时候,先比较一下要删除的key-value的value是不是和自己的线程id一样,如果不一样是不可以删除这个key-value键值对的
使用Redisson实现分布式锁
这个更简单了,只需要短短几行代码Lock()和UnLock()
引入相关依赖包:
@Autowired
private Redisson redisson;
RLock lock=redisson.getLock(“job”);
lock.tryLock(30,TimesUnit.SECONDS);
int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock>0)//如果此时库存大于0,就扣减库存,stock-1
{
stringTemplate.opsForValue("stock",stock-1+"”);
System.out.println("扣减库存成功");
}
else
{
System.out.println("库存不足,扣减失败");
}
lock.unlock();