幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

java性能优化实战使用乐观锁和无锁提高代码性能

  Lock是基于AQS(AbstractQueuedSynchronizer)实现的,AQS是用来构建Lock或其他同步组件的基础,它使用了一个int成员变量来表示state(同步状态),通过内置的FIFO队列,来完成资源获取线程的排队。
  synchronized的方式加锁,会让线程在BLOCKED状态和RUNNABLE状态之间切换,在操作系统上,就会造成用户态和内核态的频繁切换,效率就比较低。
  与synchronized的实现方式不同,AQS中很多数据结构的变化,都是依赖CAS进行操作的,而CAS就是乐观锁的一种实现。CAS
  CAS是CompareAndSwap的缩写,意思是比较并替换。
  如下图,CAS机制当中使用了3个基本操作数:内存地址V、期望值E、要修改的新值N。更新一个变量的时候,只有当变量的预期值E和内存地址V的真正值相同时,才会将内存地址V对应的值修改为N。
  如果本次修改不成功,怎么办?很多情况下,它将一直重试,直到修改为期望的值。
  拿AtomicInteger类来说,相关的代码如下:publicfinalbooleancompareAndSet(intexpectedValue,intnewValue){returnU。compareAndSetInt(this,VALUE,expectedValue,newValue);}
  比较和替换是两个动作,CAS是如何保证这两个操作的原子性呢?
  我们继续向下追踪,发现是jdk。internal。misc。Unsafe类实现的,循环重试就是在这里发生的:HotSpotIntrinsicCandidatepublicfinalintgetAndAddInt(Objecto,longoffset,intdelta){intv;do{vgetIntVolatile(o,offset);}while(!weakCompareAndSetInt(o,offset,v,vdelta));returnv;}
  追踪到JVM内部,在linux机器上参照oscpulinuxx86atomiclinuxx86。hpp。可以看到,最底层的调用,是汇编语言,而最重要的,就是cmpxchgl指令。到这里没法再往下找代码了,因为CAS的原子性实际上是硬件CPU直接保证的。templatetemplatetypenameTinlineTAtomic::PlatformCmpxchg4::operator()(Texchangevalue,Tvolatiledest,Tcomparevalue,atomicmemoryorderorder)const{STATICASSERT(4sizeof(T));asmvolatile(lockcmpxchgl1,(3):a(exchangevalue):r(exchangevalue),a(comparevalue),r(dest):cc,memory);returnexchangevalue;}
  那CAS实现的原子类,性能能提升多少呢?我们开启了20个线程,对共享变量进行自增操作。
  从测试结果得知,针对频繁的写操作,原子类的性能是synchronized方式的3倍。
  CAS原理,在近几年面试中的考察率越来越高,主要是由于乐观锁在读多写少的互联网场景中,使用频率愈发频繁。
  你可能发现有一些乐观锁的变种,但最基础的思想是一样的,都是基于比较替换并替换的基本操作。
  关于Atomic类,还有一个小细节,那就是它的主要变量,使用了volatile关键字进行修饰。代码如下,你知道它是用来干什么的吗?privatevolatileintvalue;
  答案:使用了volatile关键字的变量,每当变量的值有变动的时候,都会将更改立即同步到主内存中;而如果某个线程想要使用这个变量,就先要从主存中刷新到工作内存,这样就确保了变量的可见性。有了这个关键字的修饰,就能保证每次比较的时候,拿到的值总是最新的。乐观锁
  从上面的描述可以看出,乐观锁严格来说,并不是一种锁,它提供了一种检测冲突的机制,并在有冲突的时候,采取重试的方法完成某项操作。假如没有重试操作,乐观锁就仅仅是一个判断逻辑而已。
  从这里可以看出乐观锁与悲观锁的一些区别。悲观锁每次操作数据的时候,都会认为别人会修改,所以每次在操作数据的时候,都会加锁,除非别人释放掉锁。
  乐观锁在检测到冲突的时候,会有多次重试操作,所以之前我们说,乐观锁适合用在读多写少的场景;而在资源冲突比较严重的场景,乐观锁会出现多次失败的情况,造成CPU的空转,所以悲观锁在这种场景下,会有更好的性能。
  为什么读多写少的情况,就适合使用乐观锁呢?悲观锁在读多写少的情况下,不也是有很少的冲突吗?
  其实,问题不在于冲突的频繁性,而在于加锁这个动作上。悲观锁需要遵循下面三种模式:一锁、二读、三更新,即使在没有冲突的情况下,执行也会非常慢;如之前所说,乐观锁本质上不是锁,它只是一个判断逻辑,资源冲突少的情况下,它不会产生任何开销。
  我们上面谈的CAS操作,就是一种典型的乐观锁实现方式,我们顺便看一下CAS的缺点,也就是乐观锁的一些缺点。在并发量比较高的情况下,有些线程可能会一直尝试修改某个资源,但由于冲突比较严重,一直更新不成功,这时候,就会给CPU带来很大的压力。JDK1。8中新增的LongAdder,通过把原值进行拆分,最后再以sum的方式,减少CAS操作冲突的概率,性能要比AtomicLong高出10倍左右。CAS操作的对象,只能是单个资源,如果想要保证多个资源的原子性,最好使用synchronized等经典加锁方式ABA问题,意思是指在CAS操作时,有其他的线程现将变量的值由A变成了B,然后又改成了A,当前线程在操作时,发现值仍然是A,于是进行了交换操作。这种情况在某些场景下可不用过度关注,比如AtomicInteger,因为没什么影响;但在一些其他操作,比如链表中,会出现问题,必须要避免。可以使用AtomicStampedReference给引用标记上一个整型的版本戳,来保证原子性。乐观锁实现余额更新
  对余额的操作,是交易系统里最常见的操作了。先读出余额的值,进行一番修改之后,再写回这个值。
  对余额的任何更新,都需要进行加锁。因为读取和写入操作并不是原子性的,如果同一时刻发生了多次与余额的操作,就会产生不一致的情况。
  举一个比较明显的例子。你同时发起了一笔消费80元和5元的请求,经过操作之后,两个支付都成功了,但最后余额却只减了5元。相当于花了5块钱买了85元的东西。请看下面的时序:请求A:读取余额100请求B:读取余额100请求A:花掉5元,临时余额是95请求B:花掉80元,临时余额是20请求B:写入余额20成功请求A:写入余额95成功
  我曾经在线上遇到过一个P0级别的BUG,用户通过构造请求,频繁发起100元的提现和1分钱的提现,造成了比较严重的后果,你可以自行分析一下这个过程。
  所以,对余额操作加锁,是必须的。这个过程和多线程的操作是类似的,不过多线程是单机的,而余额的场景是分布式的。
  对于数据库来说,就可以通过加行锁进行解决,拿MySQL来说,MyISAM是不支持行锁的,我们只能使用InnoDB,典型的SQL语句如下:selectfromuserwhereuserid{id}forupdate
  使用selectforupdate这么一句简单的SQL,其实在底层就加了三把锁,非常昂贵。
  默认对主键索引加锁,不过这里直接忽略;二级索引userid{id}的nextkeylock(记录间隙锁);二级索引userid{id}的下一条记录的间隙锁。
  所以,在现实场景中,这种悲观锁都已经不再采用,第一是因为它不够通用,第二是因为它非常昂贵。
  一种比较好的办法,就是使用乐观锁。根据上面我们对于乐观锁的定义,就可以抽象两个概念:检测冲突的机制:先查出本次操作的余额E,在更新时判断是否与当前数据库的值相同,如果相同则执行更新动作重试策略:有冲突直接失败,或者重试5次后失败
  伪代码如下,可以看到这其实就是CAS。oldbalance获取selectbalancefromuserwhereuserid{id}更新动作updateusersetbalancebalance20whereuserid{id}andbalance20andbalanceoldbalance
  还有一种CAS的变种,就是使用版本号机制。通过在表中加一个额外的字段version,来代替对余额的判断。这种方式不用去关注具体的业务逻辑,可控制多个变量的更新,可扩展性更强,典型的伪代码如下:version,balancedao。getBalance(userid)balancebalancecostdao。exec(updateusersetbalancebalance20versionversion1whereuserididandbalance20andversionoldversion)Redis分布式锁
  Redis的分布式锁,是互联网行业经常使用的方案。很多同学知道是使用setnx或者带参数的set方法来实现的,但Redis的分布式锁其实有很多坑。
  在08案例分析:Redis如何助力秒杀业务中,我们演示了一个使用lua脚本来实现秒杀场景。但在现实情况中,秒杀业务通常不会这么简单,它需要在查询和用户扣减操作之间,执行一些其他业务。
  比如,进行一些商品校验、订单生成等,这个时候,使用分布式锁,可以实现更灵活地控制,它主要依赖SETNX指令或者带参数的SET指令。锁创建:SETNX〔KEY〕〔VALUE〕原子操作,意思是在指定的KEY不存在的时候,创建一个并返回1,否则返回0。我们通常使用参数更全的setkeyvalue〔EXseconds〕〔PXmilliseconds〕〔NXXX〕命令,同时对KEY设置一个超时时间。锁查询:GETKEY,通过简单地判断KEY是否存在即可锁删除:DELKEY,删掉相应的KEY即可
  根据原生的语义,我们有下面简单的lock和unlock方法,lock方法通过不断的重试,来获取到分布式锁,然后通过删除命令销毁分布式锁。publicvoidlock(Stringkey,inttimeOutSecond){for(;;){booleanexistredisTemplate。opsForValue()。setIfAbsent(key,,timeOutSecond,TimeUnit。SECONDS);if(exist){break;}}}publicvoidunlock(Stringkey){redisTemplate。delete(key);}
  这段代码中的问题很多,我们只指出其中一个最严重的问题。在多线程中,执行unlock方法的,只能是当前的线程,但在上面的实现中,由于超时存在的原因,锁被提前释放了。考虑下面3个请求的时序:请求A:获取了资源x的锁,锁的超时时间为5秒请求A:由于业务执行时间比较长,业务阻塞等待,超过5秒请求B:第6秒发起请求,结果发现锁x已经失效,于是顺利获得锁请求A:第7秒,请求A执行完毕,然后执行锁释放动作请求C:请求C在锁刚释放的时候发起了请求,结果顺利拿到了锁资源
  此时,请求B和请求C都成功地获取了锁x,我们的分布式锁失效了,在执行业务逻辑的时候,就容易发生问题。
  所以,在删除锁的时候,需要判断它的请求方是否正确。首先,获取锁中的当前标识,然后,在删除的时候,判断这个标识是否和解锁请求中的相同。
  可以看到,读取和判断是两个不同的操作,在这两个操作之间同样会有间隙,高并发下会出现执行错乱问题,而稳妥的方案,是使用lua脚本把它们封装成原子操作。
  改造后的代码如下:publicStringlock(Stringkey,inttimeOutSecond){for(;;){StringstampString。valueOf(System。nanoTime());booleanexistredisTemplate。opsForValue()。setIfAbsent(key,stamp,timeOutSecond,TimeUnit。SECONDS);if(exist){returnstamp;}}}publicvoidunlock(Stringkey,Stringstamp){redisTemplate。execute(script,Arrays。asList(key),stamp);}
  相应的lua脚本如下:localstampARGV〔1〕localkeyKEYS〔1〕localcurrentredis。call(GET,key)ifstampcurrentthenredis。call(DEL,key)returnOKend
  可以看到,reids实现分布式锁,还是有一定难度的。推荐使用redlock的Java客户端实现redisson,它是根据Redis官方提出的分布式锁管理方法实现的。
  这个锁的算法,处理了分布式锁在多redis实例场景下,以及一些异常情况的问题,有更高的容错性。比如,我们前面提到的锁超时问题,在redisson会通过看门狗机制对锁进行无限续期,来保证业务的正常运行。
  我们可以看下redisson分布式锁的典型使用代码。StringresourceKeygoodgirl;RLocklockredisson。getLock(resourceKey);try{lock。lock(5,TimeUnit。SECONDS);真正的业务Thread。sleep(100);}catch(Exceptionex){ex。printStackTrace();}finally{if(lock。isLocked()){lock。unlock();}}
  使用redis的monitor命令,可以看到具体的执行步骤,这个过程还是比较复杂的。
  无锁
  无锁(LockFree),指的是在多线程环境下,在访问共享资源的时候,不会阻塞其他线程的执行。
  在Java中,最典型的无锁队列实现,就是ConcurrentLinkedQueue,但它是无界的,不能够指定它的大小。ConcurrentLinkedQueue使用CAS来处理对数据的并发访问,这是无锁算法得以实现的基础。
  CAS指令不会引起上下文切换和线程调度,是非常轻量级的多线程同步机制。它还把入队、出队等对head和tail节点的一些原子操作,拆分出更细的步骤,进一步缩小了CAS控制的范围。
  ConcurrentLinkedQueue是一个非阻塞队列,性能很高,但不是很常用。千万不要和阻塞队列LinkedBlockingQueue(内部基于锁)搞混了。
  Disruptor是一个无锁、有界的队列框架,它的性能非常高。它使用RingBuffer、无锁和缓存行填充等技术,追求性能的极致,在极高并发的场景,可以使用它替换传统的BlockingQueue。
  在一些中间件中经常被使用,比如日志、消息等(Storm使用它实现进程内部通信机制),但它在业务系统上很少用,除非是类似秒杀的场景。因为它的编程模型比较复杂,而且业务的主要瓶颈主要在于缓慢的IO上,而不是慢在队列上。小结
  本章中,我们从CAS出发,逐步了解了乐观锁的一些概念和使用场景。
  乐观锁严格来说,并不是一种锁。它提供了一种检测冲突的机制,并在有冲突的时候,采取重试的方法完成某项操作。假如没有重试操作,乐观锁就仅仅是一个判断逻辑而已。
  悲观锁每次操作数据的时候,都会认为别人会修改,所以每次在操作数据的时候,都会加锁,除非别人释放掉锁。
  乐观锁在读多写少的情况下,之所以比悲观锁快,是因为悲观锁需要进行很多额外的操作,并且乐观锁在没有冲突的情况下,也根本不耗费资源。但乐观锁在冲突比较严重的情况下,由于不断地重试,其性能在大多数情况下,是不如悲观锁的。
  由于乐观锁的这个特性,乐观锁在读多写少的互联网环境中被广泛应用。
  今天我们主要看了在数据库层面的一个乐观锁实现,以及Redis分布式锁的实现,后者在实现的时候,还是有很多细节需要注意的,建议使用redisson的RLock。
  当然,乐观锁有它的使用场景。当冲突非常严重的情况下,会进行大量的无效计算;它也只能保护单一的资源,处理多个资源的情况下就捉襟见肘;它还会有ABA问题,使用带版本号的乐观锁变种可以解决这个问题。
  这些经验,我们都可以从CAS中进行借鉴。多线程环境和分布式环境有很多相似之处,对于乐观锁来说,我们找到一种检测冲突的机制,就基本上实现了。
  下面留一个问题,大家可以思考下:
  一个接口的写操作,大约会花费5分钟左右的时间。它在开始写时,会把数据库里的一个字段值更新为start,写入完成后,更新为done。有另外一个用户也想写入一些数据,但需要等待状态为done。
  于是,开发人员在WEB端,使用轮询,每隔5秒,查询字段值是否为done,当查询到正确的值,即可开始进行数据写入。
  开发人员的这个方法,属于乐观锁吗?有哪些潜在问题?应该如何避免?欢迎你在下方留言,一起交流讨论。

武亚楠遭TKO,赛前遭对手攻击性挑衅在刚刚的UFC278主赛中,中国女将武亚楠对战了露西普迪洛娃,但遗憾的是在第二回合4分04秒时,武亚楠遭露西的地面砸拳TKO告负。露西早在2017年就曾征战过UFC,但以……广东男篮正式重建!胡明轩或接替周鹏职位,杜锋再遭本土球迷质疑广东男篮的队长周鹏,正式的官宣加盟深圳男篮,这也预示着广东男篮即将进行大批量的裁员,而球队也正是要重建,距离CBA的球员注册截止日期还剩下10天的时间,球队也势必会做出一些果断……云海山峦绘仙境【来源:新华网】山峦在云雾中若隐若现。新华网发近日,山西省临汾市隰县迎来一场降雨。雨后初霁,隰县上空出现云海景象。云海缠绕在绵延的山脉和乡野间,宛若仙境。(亚明张瑞……世界首个白内障手术机器人,获3。7亿投资来源:器械之家,未经授权不得转载,且24小时后方可转载。ForSightRobotics于7月18日宣布完成了5500万美元A轮融资,由AdaniGroup和现有投资者E……央行降息是什么意思?降息时买什么东西会升值?降息实际就是指为市场释放更多资金流动性,对于那些需要资金流动性支撑的都会进行升值,比如买房产、股票、债券、黄金和基金等都会进行升值。面临央行降息想要进行升值,前提条件就是……秋天是养阴黄金期,建议常吃3黑2红,滋阴益气好入冬在经过大半年的活跃之后,人们在秋季也要开始对自己的身体进行适当的休养调整,而为了遵循老辈人秋冬养阴的原则,人们也会适当吃些滋养食物来及时给身体补充养分和能量。针对秋天养阴……中国癌症高发,都是味精惹的祸?提醒真正致癌的是这3种食物听说老李确诊了胃癌,经常在一起玩的好朋友老许心里很不好受,去他家里探望,老李病恹恹地跟他说:如果经常吃味精的话,还是每年都做胃肠镜吧,那东西致癌!虽然老许不确定味精是不是……华为MateBookXPro配置速看3。1K触控屏,体验堪比在最近的华为发布会上,发布了将近十款新品。其中新款MateBookXPro实力惊人,在体验方面丝毫不输苹果的MacBook。外观1。38Kg的金属机型,采用张力弧面……东华大学闻力生教授元宇宙是服装智能制造的未来归宿元宇宙是服装智能制造的未来归宿已经过去的2021年是元宇宙(MetaVerse)元年,这一年围绕元宇宙技术全球进行了狂热的炒作,其中有说它代表着未来,有说它以雪崩开始也以……居家远程办公神器,一键远程开关机,智能插线板体验当下疫情老是反复,个别地区还比较严峻,有些员工被隔离在家,出不了门,宅在家里就不能工作赚钱了么?当然能,我们可以远程办公!不要觉得远程办公是个大工程,利用向日葵的智能插线板及远……旧貌换新颜扮靓乡村新颜值【来源:文明怀柔图片新闻】时下,琉璃庙镇各处是山清水秀人忙碌、花红柳绿鸟争鸣。。。。。。一幅人与自然和谐共生的美丽画卷徐徐展开,为村民群众带来油然而生的获得感、满足感与幸……阿里最新股权曝光软银持股23。9蔡崇信持股1。4雷递网雷建平7月26日报道阿里巴巴集团今日发布2022财年年报,财报显示,截至2022年7月15日,阿里巴巴董事局副主席蔡崇信持股1。4。截至2022年7月15日,……
卡梅隆对育碧的阿凡达游戏非常满意育碧的《阿凡达》游戏《阿凡达:潘多拉边境》已经给电影导演詹姆斯卡梅隆留下了深刻的印象。近期在接受IGN采访时,这名《阿凡达》电影导演解释说他对《阿凡达:潘多拉边境》以及它……厦门银行副行长陈蓉蓉财务出身年薪131万相当不错还持股5万运营商财经网实习生杨雪利文陈蓉蓉是厦门银行目前唯一的一位女高管,她是公司一位能力很强的老将、今身兼数职。此人有何故事?运营商财经网将揭秘厦门银行副行长陈蓉蓉的过往经历。……慈善中文免装。转荐游戏更新资本。星火燎原巴斯蒂德。资源共享事前声明:此处发布的游戏普遍为绿色免安装兼自带中文翻译(繁体或者简体,两者皆有或者仅包含一种),有些来自民间第三方,有些则来自官方(无汉化的游戏也会提前进行标注);其次,你在体……和爸爸妈妈一起玩游戏真开心20210416今天上午,我们去大超市买东西。我还是挺喜欢这家超市的,毕竟这家超市的购物车真的好大、好宽敞,我坐在里面,甚至还能劈个叉哈哈!当然啦,劈叉什么的都是次要的,每回逛超市的一大……手上这个穴位,没事按一按,激发阳气商阳穴,别名绝阳,在很多古医药书里都有记载,例如《灵枢本输》、《针灸甲乙经》等,该穴位不仅能强壮补益身体,促进肠道蠕动,而且还是男姓姓功能保健的重要穴位,常按摩该穴位能强米青壮……偏头痛其实不难,但需要找对方法偏头痛不少见,尤其女性,经常被偏头痛所困扰。偏头痛治疗起来其实也不难,最主要的还是要找到合适的治疗方法。这两天碰到一位偏头痛患者,日常也经常……美媒将邓肯和库里的8项荣誉成就数据进行比较,邓肯80完爆库里斯蒂芬库里被广泛认为是有史以来最好的控球后卫之一,也是我们见过的最好的射手。作为一个连续两次获得MVP和获得4次总冠军的球员,斯蒂芬库里改变了NBA,他带动球员尽可能多地投三分……治疗失眠的粥,在家就能做,营养价值高,老人小孩都能吃治疗失眠的粥是指具有药物作用的食材混合在一起的粥,这种粥往往也被我们中医称之为药膳。药膳不仅美味而且还有药用价值,是在我国在传统饮食文化和中医食疗文化相互碰撞下诞生出来的……宿命模式见证Raid新高度!魔兽世界暗影国度第4赛季再添玩法魔兽世界暗影国度资料片的第4赛季将于8月4日准时开启,可以说这又是一个充满了创新的赛季,不少新内容让人非常有兴趣。宿命团队副本的加入让喜欢Raid的人有了更多事情可以做。……荣耀Magic4至臻版拍照如何?对比索尼和三星,感觉还差些意目前在DXO影像排名中,国产旗舰手机荣耀Magic4至臻版排名第一,超越了三星、苹果、索尼等海内外各大机型。那么荣耀Magic4至臻版拍照体验到底如何?今天就拿它与索尼Xper……不断提升认知,从而达到交易的最高级别稳定盈利(二)三、中学生阶段:概率的世界进入这个层面以后,你会发现其实没有人能准确的预期市场,市场的任何行为都是概率的结果,市场第二天可能涨,也可能跌,你只需要对可能性做出相应的计划就……河南生态景区走红,因众多画眉鸟栖息而得名,是休闲旅游的好地方河南生态景区走红,因众多画眉鸟栖息而得名,是休闲旅游的好地方自然生态环境优美的景区当中不仅仅有着让大家流连忘返的绝美景色,同样这里也有让大家感觉非常舒服的新鲜空气,行走在……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网