Java模拟并发带来的线程安全问题和解决方案(加锁)

该博客探讨了并发环境下商品库存管理的问题,通过模拟并发请求导致库存更新混乱。文章介绍了使用Synchronized关键字和ReentrantLock来解决并发控制,并进一步讨论了在微服务场景下如何利用Redis实现分布式锁,确保库存操作的正确性。最后,展示了Redis实现分布式锁的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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());
                    }
                }

            }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值