单线程无锁情况
/**
* @description 单线程情况 无任何问题,一旦涉及到多线程,
* 就涉及到先后顺序,得先查询redis获取库存,然后减库存,
* 将剩余库存存入redis,这段操作需要原子执行才行
*
* @params []
* @return void
* @author xiaoyc
* @date 2021/7/17 09:44
**/
public void skill() {
String goodsId = "goods_id";
String numStr = redisTemplate.opsForValue().get(goodsId).toString();
int num = Integer.parseInt(numStr);
//模拟购买 单次为一个商品
if (num - 1 >= 0) {
redisTemplate.opsForValue().set(goodsId, num - 1);
System.out.println("购买成功!!!");
} else {
System.out.println("库存不足!!!");
}
}
单线程情况 无任何问题,一旦涉及到多线程,
就涉及到先后顺序,得先查询redis获取库存,然后减库存,
将剩余库存存入redis,这段操作需要原子执行才行
演变一:使用redis锁
public void skill2() {
String lockKey = "goodsKillKey";
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "lockValue");
if (lock) {
try {
String goodsId = "goods_id";
String numStr = redisTemplate.opsForValue().get(goodsId).toString();
int num = Integer.parseInt(numStr);
//模拟购买 单次为一个商品
if (num - 1 >= 0) {
redisTemplate.opsForValue().set(goodsId, num - 1);
System.out.println("购买成功!!!");
} else {
System.out.println("库存不足!!!");
}
} finally {
redisTemplate.delete(lockKey);
}
} else {
//自旋
skill2();
}
}
看起来好像什么问题,实际上呢,假如在某个服务的线程拿到了锁,然后突然服务崩了。就会造成死锁。怎么解决? 最先想到的方法便是给redis key加一个默认的过期时间。如下
演变二
public void skill3() {
String lockKey = "goodsKillKey";
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "lockValue",30, TimeUnit.SECONDS);
if (lock) {
try {
String goodsId = "goods_id";
String numStr = redisTemplate.opsForValue().get(goodsId).toString();
int num = Integer.parseInt(numStr);
//模拟购买 单次为一个商品
if (num - 1 >= 0) {
redisTemplate.opsForValue().set(goodsId, num - 1);
System.out.println("购买成功!!!");
} else {
System.out.println("库存不足!!!");
}
} finally {
redisTemplate.delete(lockKey);
}
} else {
//自旋
skill3();
}
}
这样是不是就完美了呢?
答案肯定是否定的,假如第一个线程执行需要10秒钟,第二个线程需要30秒,在第一个线程执行完之后,再执行时,会不会有可能误删了别的线程的锁? 再次优化如下。
演变三
public void skill4() {
String lockKey = "goodsKillKey";
String value = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, value, 30, TimeUnit.SECONDS);
if (lock) {
try {
String goodsId = "goods_id";
String numStr = redisTemplate.opsForValue().get(goodsId).toString();
int num = Integer.parseInt(numStr);
//模拟购买 单次为一个商品
if (num - 1 >= 0) {
redisTemplate.opsForValue().set(goodsId, num - 1);
System.out.println("购买成功!!!");
} else {
System.out.println("库存不足!!!");
}
} finally {
String values = redisTemplate.opsForValue().get(lockKey).toString();
if (values.equals(value)) {
redisTemplate.delete(lockKey);
}
}
} else {
//自旋
skill4();
}
}
完美解决。撒花!!!