【Spring】高并发下如何提高“锁”性能?

news/2024/11/22 20:24:40/

高并发下如何提高“锁”性能?

  • 前言
  • 减小锁持有时间
  • 减小锁粒度
  • 读写分离锁来替换独占锁
  • 锁分离
  • 锁粗化
  • 总结

前言

在项目中,尤其是电商或者做游戏开发的,高并发是必然的,但在高并发的环境下,大家会经常使用到

“锁” 是最常用的同步方法之一。但激烈的锁竞争会导致程序的性能下降,严重的甚至能导致 “死锁”的产生。

这个时候,可能会有小伙伴会说,可以使用多线程啊。使用多线程的确可以明显地提高系统的性能。但事实上,使用多线程的方式会额外增加系统的开销。对于多线程应用来说, 系统除了处理功能需求外,还需要额外维护多线程环境的特有信息,如线程本身的元数据、线程的调度、线程上下文的切换等。

因此,合理的并发,才能将多核CPU 的性能发挥到极致。为了将这种副作用降到最低,我这里提出一些关于使用锁的建议,希望可以帮助大家写出性能更为优越的程序 。

减小锁持有时间

大家都知道,在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系

比如要求100个人填写自己的身份信息,但是只给他们一支笔。那么所需的总时间,取决于每个人填写的时间。如果每个人事先都想好所填的内容后再拿笔填写,这样每个人都会大大减少自己的填写时间,这样所需的整体时间也会大大降低。

如果把这支笔比作锁,那么减少每个人持有笔的时间,就是减小锁持有时间。大家可以看下这段代码:

public synchronized void syncMethod() { othercodel ();mutextMethod () ; othercode2 () ;
}

syncMethod()同步方法块中,如果只有mutextMethod()方法需要同步,othercodel和othercode2 不需要同步方法块控制。如果这时并发量很大,使用这种对整个方法做同步,那么会导致花费较长的CPU时间,等待线程大大增加。因为 一个线程,在进入该方法时获得内部锁,只有在所有任务都执行完后, 才会释放锁。

一个较为优化的解决方案是,只在必要时进行同步,这样就能明显减少线程持有锁的时间, 提高系统的吞吐量。

public void syncMethod2 () {othercode1 ();synchronized (this) { mutextMethod () ;}othercode2 () ;
}

在改进的代码中,只针对mutextMethod方法做了同 步 ,锁占用的时间相对较短 , 因此能有更高的并行度。

减少锁的持有时间有助于降低锁冲突的可能性,进而提升系统的并发能力。

减小锁粒度

大家应该还记得ConcurrentHashMap这个类吧。相信大家已经了解了它的原理了。它内部细分了若干个小的HashMap,称之为段(SEGMENT)。 默认情况下,一个ConcurrentHashMap 被进一步细分为 16 个段。为什么在这里提到这个类呢? 大家先思考下这个问题:

如果需要在ConcurrentHashMap 中增加 一个新的表项,并不是将整个HashMap 加锁,而是 首先根据hashcode 得到该表项应该被存放到哪个段中,然后对该段加锁,并完成put()操作。在 多线程环境中,如果多个线程同时进行put 操作,只要被加入的表项不存放在同 一个段中,则 线程间便可以做到真正的并行。

由于默认有16 个段,因此,如果够幸运的话,ConcurrentHashMap 可以同时接受 16 个线程同时插入(如果都插入不同的段中),从而大大提供其吞吐量 。

这个就是减小锁粒度的经典应用场景。

所谓减少锁粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性,进而提高系统的并发能力。

读写分离锁来替换独占锁

使用读写锁ReadWriteLock 也可以提高系统的性能,它是使用读写分离锁 来替代独占锁是减小锁粒度的一种特殊情况。如果减少锁粒度是通过分割数据 结构实现的,那么,读写锁则是对系统功能点的分割。

在读多写少的场合,读写锁对系统性能是很有好处的。因为如果系统在读写数据时均只使 用独占锁,那么读操作和写操作间、读操作和读操作间、写操作和写操作间均不能做到真正的 并发,并且需要相互等待。而读操作本身不会影响数据的完整性和一致性。因此,理论上讲, 在大部分情况下,应该可以允许多线程同时读,读写锁正是实现了这种功能。

在读多写少的场合,使用读写锁可以有效提升系统的并发能力。

锁分离

如果将读写锁的思想做进一步的延伸,就是锁分离。 读写锁根据读写操作功能 上的不同, 进行了有效的锁分离。依据应用程序的功能特点,使用类似的分离思想,也可以对独占锁进行 分离。一个典型的案例就是java.util.concurrent.LinkedBlockingQueue 的实现。

LinkedBlockingQueue 的实现中,take()函数和put()函数分别实现了从队列中取得数据和 往队列中增加数据的功能。虽然两个函数都对当前队列进行了修改操作,但由于 LinkedBlockingQueue 是基于链表的,因此,两个操作分别作用于队列的前端和尾端,从理论上 说,两者并不冲突。

如果使用独占锁,则要求在两个操作进行时获取当前队列的独占锁,那么take()和put()操作就不可能真正的并发。

因此,LinkedBlockingQueue 实现了取数据和写数据的分离,使 两者在真正意义上成为可并发的操作。

锁粗化

刚刚讲了减小锁的持有时间,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁 上的其他线程才能尽早地获得 资源执行任务。

但是,凡事都有一个度,如果对同一个锁不停地进行请求、同步和释放,其本身也会消耗系 统宝贵的资源,反而不利于性能的优化 。

为此,虚拟机在遇到 一连串连续地对同一锁不断进行请求和释放的操作时,便会把所有的 锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁的粗化。比如 代码段:

for(int i=0;i<CIRCLE;i++){synchronized (lock) {}
}

在循环内请求锁时。在这种情况下,意味着每次循环都有申请锁和释放锁的 操作。但在这种情况下,显然是没有必要的。

所以,一种更加合理的做法应该是在外层只请求一次锁:

synchronized (lock) {for (int i=0;i<CIRCLE;i++) {}
}

总结

性能优化就是根据运行时的真实情况对各个资源, 点进行权衡折中的过程。锁粗 化的思想和减少锁持有时间是相反的,但在不同的场合,它们的效果并不相同。 所以大家需要根据实际情况,进行权衡。


http://www.ppmy.cn/news/21113.html

相关文章

数据库管理-第五十五期 DBA(20230131)

数据库管理 2023-01-32第五十五期 DBA1 数据库管理员2 数据库3 云数据库4 “列强是我自己”&#xff1f;总结第五十五期 DBA 这两天在DBA圈子里有几篇文章比较火&#xff0c;《你怎么还在招聘DBA?》&#xff0c;《云数据库是不是智商税&#xff1f;》&#xff0c;《你怎么不招…

PyQt5编程基础 2.1 GUI程序的基本框架

文章目录 创建纯代码GUI程序 创建目录 新建程序 创建GUI程序的基本过程(代码分析) 导入模块 创建应用程序 创建窗体 使用窗体类的GUI程序框架 创建项目目录 窗体设计 修改窗体的windowTitle 放一个label 放一个Push Button 保存窗体 代码设计 将QtApp中的ui文…

图例legend语法及设置

(1)设置图例位置 使用loc参数 plt.legend(loc‘lower left’) 0‘best’1‘upper right’2‘upper left’3‘lower left’4‘lower right’5‘right’6‘center left’7‘center right’8‘lower center’9‘upper center’10‘center’ (2)设置图例字体 #设置字体大小 fontsi…

如果把小程序业务和研发管理都放到一个平台

伴随着互联网在中国进程的发展&#xff0c;线上研发效能及业务应用软件也不落后于时代进步的脚步&#xff0c;中国软件行业从未停止过持续的创新。 2022年&#xff0c;业务应用开发正在简化&#xff0c;研发效能也在提升&#xff0c;其中不得不提软件在协同促进、研发一体化管…

网络攻击(Cyber Attacks,也称赛博攻击)

网络攻击&#xff08;Cyber Attacks&#xff0c;也称赛博攻击&#xff09;是指针对计算机信息系统、基础设施、计算机网络或个人计算机设备的&#xff0c;任何类型的进攻动作。对于计算机和计算机网络来说&#xff0c;破坏、揭露、修改、使软件或服务失去功能、在没有得到授权的…

【数据结构】动图详解单向链表

目录 1.什么是链表 1.问题引入 2. 链表的概念及结构 3. 问题解决 2.单向链表接口的实现 1.接口1&#xff0c;2---头插&#xff0c;尾插 2. 接口3&#xff0c;4---头删&#xff0c;尾删 3. 接口5---查找 4. 接口6&#xff0c;7---插入&#xff0c;删除 5. 接口8---打印 6. 注意…

【Unity3D插件】UniRx(基于Unity的响应式编程框架)插件教程

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群&#xff1a;1040082875 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、介绍UniRx插件 UniRx是一种基于Unity3D的…

文本数据预处理:可能需要关注这些点

文章目录1、文本数据获取2、常规文本数据预处理2.1 将文本数据清洗干净2.2 将文本数据格式化3、任务相关的文本数据预处理3.1 不平衡问题3.2 数据增强问题3.3 数据标注问题4、一些可用的文本预处理工具5、总结本文关键词&#xff1a; 文本数据预处理、中文文本预处理、自然语言…