前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一段简单代码在并发环境下的优化思路

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

作者头像
java达人
发布2018-07-31 17:02:07
3150
发布2018-07-31 17:02:07
举报
文章被收录于专栏:java达人java达人

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

代码语言:javascript
复制
 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);

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

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

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

代码语言:javascript
复制
  @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);

   }

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

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

代码语言:javascript
复制
    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,如果更新失败,则重试,这里考虑可以使用自旋机制。

代码语言:javascript
复制
  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;
    }
      
   }

  }

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

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

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

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