案例:电商平台下订单修改商品的数量,数量值放在redis中
int num=(Integer) redisUtils.get("product") - 1;
boolean set = redisUtils.set("product", num); //redisUtils是我封装的redisTemplet,效果是一样的
if((Integer)redisUtils.get("product")>0){
int num=(Integer) redisUtils.get("product") - 1;
boolean set = redisUtils.set("product", num);
System.out.println((set==true?"执行成功!":"执行失败!")+"商品的数量为"+num);
}
else{
System.out.println("库存不足,操作失败");
}
并发问题:你当前读取的数量,下一刻就不是这个值了
- 方法:使用synchronized,改为同步执行 弊端:集群环境下不适用,比如你使用负载均衡,启动两个tomcat,无效
- 优化:解决上面的弊端:使用redis作为分布式锁,redis中的setnx指令,在springboot中
boolean flag=redisTemplate.opsForValue().setIfAbsent(“lock”, “lock”);
当flag为true时,执行我们的业务代码,执行完释放(删除)锁。为false时,表示锁被抢占了,返回错误信息,请稍后再试之类的。
弊端:程序中发生异常,导致锁没释放,造成死锁,会一直操作失败。
-
优化:try、catch,最后finally放删除锁的代码,保证一定执行
弊端:finally的弊端明显,退出jvm或者死机都会执行不了 -
优化:给锁加过期时间,例如30秒钟,就算挂掉了也会把锁清除
弊端:(锁失效)某些情况下,第一个线程30秒还没执行完这个方法,锁却被redis释放了,那么别的线程进来,执行到半路上,第一个线程执行完,删除锁,这样第二个线程锁被第一个线程给清掉了,这样就相当于于没加锁一样,还是会并发问题。 -
优化:给锁加上唯一标识到value上,uuid或者userid什么的都行,删除之前判断这个标识,这样保证释放的是自己加的锁。
弊端:做到上面已经算是比较完善了,中小型企业完全撑得住,但是上面并没有从根本上解决执行时间过长,锁失效的问题,加长过期时间?时间太长导致别的线程一直在外面等待,不太好吧! -
优化:锁续命,开一个定时任务,(或者让获得锁的线程开启一个守护线程)每10秒钟检查锁,如果10秒前后检查的锁是一样的,那么就说明这把锁10秒钟还没释放掉,把这把锁的过期时间重新设置为30s
解决的原理说完了,那么请出redisson,它已经做好了一切。
启动类加上:
@Value("${redisson.addr}")
private String redisAddr;
@Bean
public Redisson redisson(){
//单机模式
Config config=new Config();
config.useSingleServer().setAddress(redisAddr).setDatabase(0);
return (Redisson) Redisson.create(config);
}
配置文件:
redisson.addr=redis://192.168.157.128:6379
controller层:
@Autowired
private Redisson redisson;
接口里面:
RLock lock = redisson.getLock("gkl"); //1
try{
lock.lock(); //2
if((Integer)redisUtils.get("product")>0){
int num=(Integer) redisUtils.get("product") - 1;
boolean set = redisUtils.set("product", num);
System.out.println((set==true?"执行成功!":"执行失败!")+"商品的数量为"+num);
}
else{
System.out.println("库存不足,操作失败");
}
}
finally {
lock.unlock(); //3
}
三行代码搞定。