1.没有并发的场景
@RequestMapping("/test")
public String deductStock() throws InterruptedException {
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
// 如果库存大于0
Integer realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock" , realStock.toString());
System.out.println("扣除成功,剩余:" + realStock);
} else {
System.out.println("扣除失败,不足");
}
return "success";
}
2.存在并发的场景(加jvm级别的锁)-单点的时候可用
@RequestMapping("/test")
public String deductStock() throws InterruptedException {
synchronized (this){
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
// 如果库存大于0
Integer realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock" , realStock.toString());
System.out.println("扣除成功,剩余:" + realStock);
} else {
System.out.println("扣除失败,不足");
}
return "success";
}
}
3.存在并发的场景(在redis层面进行加锁-利用redis单线程内存模型)-集群的时候---基本可用
@RequestMapping("/test")
public String deductStock() throws InterruptedException {
// setnx(防止key重复被覆盖数据) ; try-finally释放锁(防止执行过程中异常导致死锁,不能防止宕机的场景) ;加过期时间、原子化 防止宕机(防止死锁);
String lockkey = "lock";
try {
// 高版本可用
// Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "zmc", 30L, TimeUnit.SECONDS);
// 不保证原子性
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "zmc");
stringRedisTemplate.expire(lockkey, 30, TimeUnit.SECONDS);
if(!result){
return "error";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
// 如果库存大于0
Integer realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock" , realStock.toString());
System.out.println("扣除成功,剩余:" + realStock);
} else {
System.out.println("扣除失败,不足");
}
} finally {
stringRedisTemplate.delete(lockkey);
}
return "success";
}
4.一般高并发的场景(失效时间设置不合理:锁失效了代码还没执行完毕,finally中的delete会干掉其他线程的锁,可能导致所有的锁永久失效)(有可能超时:代码每执行完就锁过期了)
@RequestMapping("/test")
public String deductStock() throws InterruptedException {
// setnx(防止key重复被覆盖数据) ; try-finally释放锁(防止执行过程中异常导致死锁,不能防止宕机的场景) ;加过期时间、原子化 防止宕机(防止死锁);
String lockkey = "lock";
// 用于:每个线程只释放自己的锁
String clientId = UUID.randomUUID().toString();
try {
// 高版本可用
// Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "zmc", 30L, TimeUnit.SECONDS);
// 不保证原子性
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockkey, "zmc");
stringRedisTemplate.expire(lockkey, 30, TimeUnit.SECONDS);
if(!result){
return "error";
}
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
// 如果库存大于0
Integer realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock" , realStock.toString());
System.out.println("扣除成功,剩余:" + realStock);
} else {
System.out.println("扣除失败,不足");
}
} finally {
if(clientId.equals(stringRedisTemplate.opsForValue().get(lockkey))){
stringRedisTemplate.delete(lockkey);
}
}
return "success";
}
5.锁的时间设置不合理,因为不可预料所以不可能设置合理,所以需要有一个机制-每到锁的过期时间三分之一的时间的时候,重新设置过期时间
使用redission
@RequestMapping("/test")
public String deductStock() throws InterruptedException {
// setnx(防止key重复被覆盖数据) ; try-finally释放锁(防止执行过程中异常导致死锁,不能防止宕机的场景) ;加过期时间、原子化 防止宕机(防止死锁);
String lockkey = "lock";
// 用于:每个线程只释放自己的锁
String clientId = UUID.randomUUID().toString();
RLock redissonLock = redisson.getLock(lockkey);
try {
redissonLock.lock(30, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
// 如果库存大于0
Integer realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock" , realStock.toString());
System.out.println("扣除成功,剩余:" + realStock);
} else {
System.out.println("扣除失败,不足");
}
} finally {
redissonLock.unlock();
}
return "success";
}