首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

性能优化-锁的正确选型

锁选择不当带来的影响:在高并发环境下,如果锁的选取不当,直接带来的影响是,系统的吞吐量上不去,高并发成为空谈。

一、锁的分类

在底层实现上来讲,锁一共分为两类:互斥锁和自旋锁。其他上层锁都是基于底层的这两类锁来实现的。

互斥锁:加锁成本比较高,是在内核态完成的,要经过两次上下文切换,第一次是加锁失败后线程进入休眠释放cpu资源,第二次是等锁释放后,通知失败线程获取锁恢复执行。一次上下文切换的时间大概在几纳秒或者几十微秒,可能要比锁住的代码段执行的时间要长。如图所示:

自旋锁:自旋锁正好相反,加锁失败后,并不会释放cpu资源,不会进入休眠状态。而是开启一个循环调用,通过CPU提供的函数CAS(compare and swap)在用户态完成加锁和锁的释放。这种方式也通常称之为“忙等待”。例如java中读写锁的实现:如图

二、悲观锁vs乐观锁(无锁编程)

不论是互斥锁、自旋锁,还是基于以上两种锁实现的上层锁如读写锁等,都是悲观锁。悲观锁认为同时修改资源的概率比较高,因此在访问资源前,先进行加锁,总体效率会比较高。乐观锁,更是一种乐观的思想,认为并发冲突很小,无需加锁,在更新数据时做判断,通常有两种实现方式:版本号、上文提到的CAS(注意上文使用cas是加锁,这里是直接改数据,并没有锁),这种思想也被称为无锁编程。

三、互斥锁、自旋锁、乐观锁(无锁编程)该如何选择

读到这里,我们知道了,互斥锁是通过“线程切换”来应对获取锁失败,自旋锁是通过“忙循环”不释放cpu资源两种不同的思想方式。其他上层的锁应用都是基于这两类锁实现的。java中我们常用的读写锁既可以通过互斥锁实现,也可以通过自旋锁实现。读写锁的优势在于:在写锁没有持有时,多线程可以并发的持有读锁,提高了共享资源的使用率。

根据业务场景选型

如果针对我们的业务,能够明确的区分出“读”和“写”的场景,那么可以使用读写锁。如果能根据埋点统计读写的比例,当读比例比较高时,可以使用jdk8提供的StampedLock,该版本对可以实现乐观读,通过增加了版本号的方式,节省了cas忙循环的等待,因为没有cas操作所以效率会比较高。

当我们不能预判出被锁住代码的执行时长,或执行时长比较长,则适合使用悲观锁

如果我们确定被锁住代码的执行时长比较短,可以选取自旋锁

当并发冲突比较低的场景下,使用乐观锁进行无锁编程。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201113A0CDW000?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券