1.背景
在数据库建一张商品库存表,这里模拟库存为5个,在正常的程序逻辑中,应该先查库存表,
当库存表中的库存数量大于0的时候,才能下单成功。但是在并发情况下,多个线程同时请
求共享资源,会导致共享资源出现资源混乱,具体看下面演示。
2.部分程序逻辑代码
@RequestMapping("/concurrent")
public String testConcurrent() throws InterruptedException {
//先查库存
Goods goods = goodsDao.selectById(1);
//Thread.sleep(3000);
if (goods.getGoodStore()>0){
int i = goods.getGoodStore() - 1;
goods.setGoodStore(i);
Integer integer = goodsDao.updateAllColumnById(goods);
Goods goods1 = goodsDao.selectById(1);
System.out.println("抢到商品,当前减后库存是"+goods1.getGoodStore());
return "成功";
}else {
System.out.println("当前没抢到线程是"+Thread.currentThread().getName());
return "没有库存了";
}
3.使用ApiFox模拟并发请求
我这里模拟10个线程同时去请求我的接口,正常情况下来说,我的库存只有5个,那
这段代码应该就只会打印5遍
System.out.println("抢到商品,当前减后库存是"+goods1.getGoodStore());
ApiFox模拟并发 发送请求;
查看控制台结果,此时程序出现了混乱了,正常应该只打印5遍,这里由于并发情况下,多个线程同时请求共享资源,造成错乱了,打印了10遍,肯定不对,正常应该只打印5遍,我的库存都只有5个,怎么可能10个显示抢到商品,这个时候可以加锁,有俩种基本的方法,继续往下看。
使用Synchronize关键字
再来模拟请求,观察控制台输出
//先查库存
synchronized (this){
Goods goods = goodsDao.selectById(1);
//Thread.sleep(3000);
if (goods.getGoodStore()>0){
System.out.println("当前库存是"+goods.getGoodStore());
int i = goods.getGoodStore() - 1;
goods.setGoodStore(i);
Integer integer = goodsDao.updateAllColumnById(goods);
Goods goods1 = goodsDao.selectById(1);
System.out.println("抢到商品,当前减后库存是"+goods1.getGoodStore());
return "成功";
}else {
System.out.println("当前没抢到线程是"+Thread.currentThread().getName());
return "没有库存了";
}
}
正常打印5遍
使用ReentrantLock类提供的lock方法进行加锁
//1.实例化lock锁 成员变量(不是方法私有变量)
private ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
Goods goods = goodsDao.selectById(1);
Thread.sleep(3000);
if (goods.getGoodStore()>0){
System.out.println("当前库存是"+goods.getGoodStore());
int i = goods.getGoodStore() - 1;
goods.setGoodStore(i);
Integer integer = goodsDao.updateAllColumnById(goods);
Goods goods1 = goodsDao.selectById(1);
System.out.println("抢到商品,当前减后库存是"+goods1.getGoodStore());
}else {
System.out.println("当前没抢到线程是"+Thread.currentThread().getName());
}
}catch (Exception e){
log.info("减库存过程出错,",e);
}finally {
lock.unlock();
}
加锁之后,程序正常执行,不会出现的错乱的情况,但是这种只适用于单体项目,对于微服务项目,应该使用分布式锁,但是原理还是一样的。
4.redis分布式锁
//使用redis实现分布式锁setnx
Boolean flag = redisTemplate.opsForValue().setIfAbsent(Good_Module, Thread.currentThread().getId(), 5, TimeUnit.SECONDS);
//如果拿到了锁,执行业务逻辑
if (flag) {
try {
log.info("线程{}拿到锁,开始执行业务逻辑",Thread.currentThread().getId());
Goods goods = goodsDao.selectById(1);
//锁过期时间是5秒 这里我让线程休眠5s 那键值肯定就是过期了 看看有什么问题
//Thread.sleep(5000);
if (goods.getGoodStore() > 0) {
log.info("当前库存是{}", goods.getGoodStore());
int i = goods.getGoodStore() - 1;
goods.setGoodStore(i);
Integer integer = goodsDao.updateAllColumnById(goods);
Goods goods1 = goodsDao.selectById(1);
log.info("线程{}抢到商品,当前减后库存是{}", Thread.currentThread().getId(), goods1.getGoodStore());
}
}catch (Exception e){
log.info("扣除库存中出现异常",e);
}finally {
//谁加锁 谁解锁
if (Objects.equals(redisTemplate.opsForValue().get(Good_Module),Thread.currentThread().getId())){
redisTemplate.delete(Good_Module);
log.info("线程{}解锁成功",Thread.currentThread().getId());
}
}
}