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

问题背景:

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

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

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)尽量减小锁的代码范围(在代码块中加锁就能解决问题的就不要在接口方法上加锁)。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏WindCoder

自用插件整理之表格bootstrap-table

本插件基于bootstrap,网上各种例子也比较多,本文就不详细列api一类的了,只将自己常用的记录一下。多数代码中存在的注释,就不再重写。

1.4K10
来自专栏程序员八阿哥

绝了!Python又放大招,逆袭宇宙语言!

自2017年国务院印发《新一代人工智能发展规划》,明确指出在中小学阶段设置人工智能相关课程后,Python一路逆袭, 作为人工智能时代最合适的语言,Python...

22020
来自专栏草根专栏

ASP.NET Core的实时库: SignalR简介及使用

SignalR是一个.NET Core/.NET Framework的开源实时框架. SignalR的可使用Web Socket, Server Sent Ev...

19510
来自专栏WindCoder

为WordPress加入Fancybox相册功能免插件实现

可以在js里面建个相应文件夹放置,也可分开放置,若图片与其他文件分开记得修改css里面的图片链接地址。

32410
来自专栏Python小屋

Python爬虫基础:常用HTML标签和Javascript入门

大部分HTML标签是闭合的,由开始标签和结束标签构成,二者之间是要显示的内容,例如:<title>网页标题</title>。也有的HTML标签是没有结束标签的,...

20910
来自专栏WindCoder

JSON中关于对双向关联的支持

本文原文:Bidirectional Relationship Support in JSON

24320
来自专栏用户2442861的专栏

mysql出现错误“ Every derived table must have its own alias”

http://blog.sina.com.cn/s/blog_5d2eee260100xu8b.html

3.3K10
来自专栏Python小屋

Python使用标准库urllib模拟浏览器爬取网页内容

爬取网页内容的第一步是分析目标网站源代码结构,确定自己要爬取的内容在哪里,这要求对HTML代码有一定了解,对于某些网站内容的爬取还需要具有一定的Javascri...

14610
来自专栏用户2442861的专栏

JSON 入门指南(IBM)

尽管有许多宣传关于 XML 如何拥有跨平台,跨语言的优势,然而,除非应用于 Web Services,否则,在普通的 Web 应用中,开发者经常为 XML 的...

13810
来自专栏跟着阿笨一起玩NET

为什么浏览器不能跨域

 现在很多人特别是前端开发人员,在ajax请求,XMLHttpRequest的过程中会碰到一个问题,那就是跨域请求:

45310

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励