前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >synchronized锁处理spring事务高并发请求

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

作者头像
echobingo
发布2018-10-15 13:08:42
3.7K0
发布2018-10-15 13:08:42
举报
文章被收录于专栏:Bingo的深度学习杂货店

问题背景:

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

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

ServiceImpl.java
代码语言:javascript
复制
@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 里,用法如下:

代码语言:javascript
复制
synchronized {
      Integer currentAttendCount = activity.getAttendCount();  // 从数据库中得到当前已经报名的人数
      //.......
      return json;
}

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

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

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

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

Controller.java
代码语言:javascript
复制
@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)尽量减小锁的代码范围(在代码块中加锁就能解决问题的就不要在接口方法上加锁)。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.10.13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题背景:
    • ServiceImpl.java
    • 问题解决:synchronized 关键字
      • Controller.java
      • 问题总结
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档