一段简单代码在并发环境下的优化思路

有一段简单的代码,主要功能是根据好友的注册等活动来计算邀请者本人的贡献分,因此,每次有新的好友参与活动都会触发下面的方法:

 public void  calculateIScore(String friendId){ 
   //获取邀请者本人
    Person person = personMapper.selectInviter(friendId); 
   
   //根据相关好友活动计算邀请者贡献分
    int score= calculateScoreByAllFriendAct(person)

    person.setScore(score)

    personMapper.updateScore(person);

 }

一开始,并发量不高,几乎每次运行都是单个线程,因此计算得出的分数都是正确的。但随着用户邀请量的激增,以及好友活动记录的频繁插入,使这个方法时常暴露在并发环境下。这就导致了一个问题:

假设好友A先注册,然后好友B注册,几乎同时触发了calculateIScore方法:

好友A先注册

好友B后注册

注册成功

触发方法 calculateIScore()

获取邀请者,计算设置分数 int score=calculateScoreByFriendAct(person) person.setScore(score)

注册成功

触发方法 calculateIScore()

获取邀请者,计算设置分数 int score= calculateScoreByFriendAct(person) person.setScore(score)

更新贡献分personMapper.updateScore(person);

更新贡献分personMapper.updateScore(person);

这就导致根据共享数据计算数据值的时候,旧值覆盖最新值的现象,用户总是抱怨贡献分有时候会突然减少。

怎么改?一个简单的思路就是加独占锁。

比较通用的加锁方式是对数据库记录加行锁,并且配置事务。

  @Transaction
   public void  calculateIScore(String friendId){  
  
   //获取邀请者本人,Mapper文件中sql语句改为select from ...for update 形式。
    Person person = personMapper.selectInviterForUpdate(friendId);  
   
    //根据相关好友活动计算邀请者贡献分
    int score= calculateScoreByFriendAct(person)

    person.setScore(score)

    personMapper.updateScore(person);

   }

这样无论是在单个服务还是多个服务部署环境下,都可以实现分布式锁的效果。

在单服务环境下,简单地加个锁也可以。

    public void  calculateIScore(String friendId){  
        
     synchronized(this){ 

      Person person = personMapper.selectInviter(friendId);
           
     //根据相关好友活动计算邀请者贡献分
      int score= calculateScoreByFriendAct(person)

      person.setScore(score)

      personMapper.updateScore(person);

     }

    }

关于分布式锁,还可以借助zookpeeper,redis等组件实现。

zookeeper分布式锁可参考早期文章

ZooKeeper构建分布式锁(选译)

redis锁实现思路很多,如锁命令INCR,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作进行加一。 然后其它用户在执行 INCR 操作进行加一时,如果返回的数大于 1 ,说明这个锁正在被使用当中。

独占锁相对比较安全,但严重影响性能,线程阻塞和唤醒的开销都很大。

因此我们可以考虑使用非阻塞方式,实现思路可以参考原子类的cas机制。即借助冲突检查机制判断在更新过程中是否存在来自其他线程的干扰,如果存在,操作失败,且可以重试。CAS指令需要有3个操作数,分别是内存位置(在Java中可以简单理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,当且仅当V符合旧预期值A时,处理器用新值B更新V的值,否则它就不执行更新,上述的处理过程是一个原子操作。

参考它的实现思路,我们可以给表加个版本号,查询时会取得当前记录的版本号,当更新时在where条件中判断版本号是否发生了变化,并且将版本号加1,如果更新失败,则重试,这里考虑可以使用自旋机制。

  public void  calculateIScore(String friendId){   
  
   //重试10次
  for(int i=0;i<10;i++){

    Person person = personMapper.selectInviter(friendId);   
   
   //根据相关好友活动计算邀请者贡献分
    int score= calculateScoreByFriendAct(person)

    person.setScore(score)    
   
 //相应sql语句改为
 //update person set version=version+1 ... where id=#{id} and version=#{version}形式
    int updateNum = personMapper.updateScoreByIdAndVersion(person);     
    if(updateNum>0){    
      break;
    }
      
   }

  }

这样,更新时如果发现版本号变化,说明其他线程已经对记录作了更新操作,重试,再次计算得出最新值。

这里只提供一些思路,具体编码的时候还有很多要注意的地方,各位看官有类似的经验欢迎留言。

原文发布于微信公众号 - java达人(drjava)

原文发表时间:2018-06-02

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏申龙斌的程序人生

零基础学编程004:集成开发环境IDE

几天前介绍了《用在线编程环境快速上手》学习Python等编程语言,这种教学环境中的例子都非常简单,你不需要在自己的电脑中安装任何的软件,就可以马上动手学习Pyt...

3215
来自专栏FreeBuf

微信也被监控:HackingTeam语音监控代码分析

在HackingTeam泄漏的文件,我们发现了有针对主流聊天软件中的语音进行监控的代码,其中包括国内常用的微信。下面就以微信为例,来分析一下HackingTea...

2958
来自专栏Zephery

谈谈个人网站的建立(四)—— 日志系统的建立

谈谈个人网站的建立(四)—— 日志系统的建立 欢迎访问我的网站http://www.wenzhihuai.com/ 。感谢,如果可以,希望能在GitHub上给个...

3664
来自专栏mathor

软件破解逆向工程实战(一)

本系列教程无需任何基础,直接学习即可,对于没有c/c++基础的同学来说也没有什么坎,多看,多做就能掌握,同时说一下,我们的QQ群:689696631,因为本系列...

1862
来自专栏社区的朋友们

HBase 学习分享

有些时候你可曾面对产品看似普通且合理的需求,例如:1、能否让网页活动拉取用户的游戏好友关系链从而更精准的推送Tips? 2、判断用户是否在所有大区都没有角色这类...

7220
来自专栏程序员的知识天地

JavaScript设计模式与实践--适配器模式

适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协...

1951
来自专栏前端杂货铺

deno深入揭秘及未来展望

node.js之父Ryan Dahl在一个月前发起了名为deno的项目,项目的初衷是打造一个基于v8引擎的安全的TypeScript运行时,同时实现HTML5...

1871
来自专栏逆向技术

学习逆向知识之用于游戏外挂的实现.第三讲,通过游戏外挂.分析红色警戒金钱基址.以及确定基址小技巧.

                          分析红色警戒金钱基址.以及确定基址小技巧.

1171
来自专栏博客园迁移

工作中的一些经验小结

控制层   返回值统一 AOP 也方便前台 AOP 统计执行时间记录日志   参数不要出现Request, Response   返回码定义 不...

732
来自专栏云计算教程系列

如何在Ubuntu 14.04上为IRC安装Lita Chat Bot

许多现代DevOps团队在聊天室周围建立了越来越多的基础设施。有很多聊天室,从商业选项(如HipChat和Slack)到DIY选项(如IRC或Jabber / ...

741

扫码关注云+社区

领取腾讯云代金券