美文网首页Java 杂谈
synchronized锁处理spring事务高并发请求

synchronized锁处理spring事务高并发请求

作者: 牛奶芝麻 | 来源:发表于2018-10-13 20:29 被阅读188次

    问题背景:

    最近在写一个活动报名功能,会有多个人同时报名某个活动,要求当参与人数超过限制人数的时候,就报出人数已满的信息。

    不考虑并发性,正常的逻辑如下:

    ServiceImpl.java
    @Override
    public JSONObject signupActivity(Integer actId, String userId) {
            // 前面的逻辑省略.......       
    
            Integer currentAttendCount = activity.getAttendCount();  // 从数据库中得到当前已经报名的人数
    
            if (currentAttendCount >= attendLimit) {   // 如果已经报名的人数超过限制人数
                json.put(CommonConst.MESSAGE, "报名人数已满");
                return json;
            }
    
            // 修改活动已参加的人数并更新数据库表中的这个字段
            Integer attendCount = currentAttendCount + 1;
            activity.setAttendCount(attendCount);
            activityMapper.updateByPrimaryKeySelective(activity);
    
            // 往报名表里添加一条用户信息......
    
            json.put(CommonConst.MESSAGE, "活动报名成功");
            return json;
    }
    

    但是,在高并发下,这段代码就会有问题。比如现在有 A, B, C 三个学生同时报名,他们从数据库中得到的 currentAttendCount 字段都是20(前面已经报名了20人),而限制报名人数 attendLimit 是 22 人,那么代码中的 if 条件都不会执行,这样问题就出现了。

    刚开始,设置了一个活动的限制人数 attendLimit 为 480。在 Jmeter 中进行测试,每秒开 500 个线程(每秒线程数 TPS = 500),报名了 500 人(数据库中有 500条记录),但是由于上述原因, currentAttendCount 并不是 500,而且远远小于 500。这样本来到了 480 人就应该提示报名已满,但是现在并不会停止,还可以继续报名。

    在网上查了一下解决办法,也试了试乐观锁、悲观锁这些,但是效果并不好。突然 get 到 Java 中的 synchronized 关键字

    问题解决:synchronized 关键字

    因为 synchronized 关键字可以修饰代码块,所以第一次我就把函数里面会出现并发问题的代码包含在 synchronized 里,用法如下:

    synchronized {
          Integer currentAttendCount = activity.getAttendCount();  // 从数据库中得到当前已经报名的人数
          //.......
          return json;
    }
    

    重新测试,结果发现好了一些(currentAttendCount 虽然仍然不是 500,但已经很接近500了)。

    上网一查,在代码块中加入 synchronized 还是不能完全解决高并发问题。原因是synchronized 代码块的执行是在事务之内执行的,可以推断在 synchronized 代码块执行完时,事务还未提交,其他线程进入 synchronized 的代码块后,读取的库存数据不是最新的。

    因此,可以将 synchronized 关键字加入到控制层 Controller 层,使 synchronized 锁的范围大于事务控制的范围。

    来到对应的控制层 Controller,找到调用上述函数的接口,在接口方法上加上 synchronized 关键字,问题解决,完美!代码如下所示:

    Controller.java
    @RequestMapping(value = "/signup", method = RequestMethod.POST)
    @ResponseBody
    public synchronized JSONObject signupActivity(@RequestBody HashMap<String, Object> reqData) {
        return participationService.signupActivity(actId, userId);  // 包含并发操作的上面那个函数
    }
    

    问题总结

    有一篇博客给我们总结了几点,我觉得很好 spring(基础18) Sprin事务和synchronized锁的一些问题,以下是引用:

    以上事务与锁之间存在的问题是:由于事务范围大于锁代码块范围,在锁代码块执行完成后,此时事务还未提交,导致此时进入锁代码块的其他线程,读到的仍是原有的库存数据。所以,要保证锁范围大于代码块范围才行。

    关于程序加锁自己的一点见解:

    • 建议程序中尽量不要加锁;
    • 尽量在业务和代码层,解决线程安全的问题,实现无锁的线程安全;
    • 如果以上两点都做不到,一定要加锁,尽量使用 java.util.concurrent 包下的锁(因为是非阻塞锁,基于CAS算法实现,具体可以查看AQS类的实现);
    • 如果以上三点仍然都做不到,一定要加阻塞锁:synchronized 锁,两个原则:
      (1)尽量减小锁粒度;
      (2)尽量减小锁的代码范围(在代码块中加锁就能解决问题的就不要在接口方法上加锁)。

    相关文章

      网友评论

        本文标题:synchronized锁处理spring事务高并发请求

        本文链接:https://www.haomeiwen.com/subject/rwqaaftx.html