JAVA高并发——锁的优化

文章目录

  • 1、减少锁持有时间
  • 2、减小锁粒度
  • 3、用读写分离锁来替换独占锁
  • 4、锁分离
  • 5、锁粗化

锁是最常用的同步方法之一。在高并发的环境下,激烈的锁竞争会导致程序的性能下降,因此我们有必要讨论一些有关锁的性能的问题,以及一些注意事项,比如避免死锁、减小锁粒度、锁分离等。

在多核时代,使用多线程可以明显地提高系统的性能,但是也会额外增加系统的开销。

对于单任务或者单线程的应用而言,其主要资源消耗都源自任务本身。它既不需要维护并行数据结构间的一致性状态,也不需要为线程的切换和调度花费时间。但对于多线程应用来说,系统除了处理功能需求,还需要额外维护多线程环境的特有信息,如线程本身的元数据、线程的调度和线程上下文的切换等。

事实上,在单核CPU上采用并行算法的效率一般要低于原始的串行算法的效率,并行计算之所以能提高系统的性能,并不是因为它“少干活”了,而是因为它可以更合理地进行任务调度,充分利用各个CPU的资源。因此,只有合理设计并发,才能将多核CPU的性能发挥到极致。

1、减少锁持有时间

对于使用锁进行并发控制的应用程序而言,在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接关系。线程持有锁的时间越长,那么相对地,锁的竞争程度也就越激烈。可以想象一下,如果要求100个人各自填写自己的身份信息,但是只给他们一支笔,那么如果每个人拿持笔的时间都很长,总体所花的时间就会很长。如果真的只有一支笔共享给100个人用,那么最好让每个人花尽量少的时间持笔,务必做到想好了再落笔,千万不能拿着笔才去思考信息应该怎么填。程序开发也是类似的,应该尽可能地减少对某个锁的持有时间,以降低线程间互斥的可能性。以下面的代码段为例:
在这里插入图片描述
在syncMethod()方法中,假设只有mutextMethod()方法是有同步需要的,而othercode1()方法和othercode2()方法并不需要做同步控制。如果othercode1()方法和othercode2()方法分别是重量级的方法,则会花费较长的CPU时间。如果在并发量较大时,使用这种对整个方法做同步的方案,则会导致等待线程大量增加。因为一个线程在进入该方法时获得内部锁,只有在所有任务都执行完后,才会释放锁。

一个较为优化的解决方案是,只在必要时进行同步,这样就能明显减少线程持有锁的时间,提高系统的吞吐量。
在这里插入图片描述
改进的代码只针对mutextMethod()方法做了同步,锁占用的时间相对较短,因此能有更高的并行度。这种技术手段在JDK的源码包中也很容易见到,比如处理正则表达式的Pattern类。
在这里插入图片描述
matcher()方法有条件地进行锁请求,只有在表达式未编译时,进行局部加锁。这种处理方式大大提高了matcher()方法的执行效率和可靠性。

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

2、减小锁粒度

减小锁粒度也是一种削弱多线程锁竞争的有效手段。这种技术的典型使用场景就是ConcurrentHashMap类的实现。对于一个线程安全的HashMap而言,在进行并发put()操作时,锁住整个Map自然是最简单也是最安全的一种实现,然而这种简单的实现却有着极其糟糕的并发性能。试想,如果在操作过程中,可以锁住局部而非整体,是不是可以既做到线程安全,又提高并行度呢?实际上,当ConcurrentHashMap插入元素时,它并不会锁住整个HashMap,而是仅仅在要操作的那个Node上加锁,因此在同一时刻,可以有多个不同的线程同时对Map进行操作而不产生数据冲突。它的伪代码如下:
在这里插入图片描述
注意:所谓减小锁粒度,就是缩小锁定对象的范围,从而降低锁冲突的可能性,进而提高系统的并发能力。

3、用读写分离锁来替换独占锁

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

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

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

4、锁分离

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

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

如果使用独占锁,则要求在两个操作进行时获取当前队列的独占锁,那么take()方法和put()方法就不可能真正并发执行,在运行时,它们会彼此等待对方释放锁资源。在这种情况下,锁竞争会相对比较激烈,从而影响程序在高并发时的性能。

因此,在JDK的实现中并没有采用这样的方式,取而代之的是用两把不同的锁分离take()方法和put()方法:
在这里插入图片描述
以上代码片段定义了takeLock和putLock,它们分别在take()方法和put()方法中使用。因此,take()方法和put()方法就此相互独立,它们之间不存在锁竞争关系,只在take()方法和take()方法间、put()方法和put()方法间存在对takeLock和putLock的竞争,从而削弱了锁竞争的可能性。

take()方法的实现如下,笔者在代码中给出了详细的注释,故不在正文中做进一步说明了:
在这里插入图片描述
put()方法的实现如下:
在这里插入图片描述
通过takeLock和putLock两把锁,LinkedBlockingQueue实现了读数据和写数据的分离,使两者在真正意义上成为可并发的操作。

5、锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早地获得资源去执行任务。但是,凡事都有一个度,如果对同一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。

为此,虚拟机在遇到一连串连续地对同一个锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步的次数,这个操作叫作锁粗化。示例如下:
在这里插入图片描述
上面的代码段会被整合成如下形式:
在这里插入图片描述
在开发过程中,大家也应该有意识地在合理的场合进行锁粗化,尤其是在循环内请求锁时。以下是一个在循环内请求锁的例子,在这种情况下,意味着每次循环中都有请求锁和释放锁的操作,但显然是没有必要的:
在这里插入图片描述
所以,一种更加合理的做法应该是只在外层请求一次锁:
在这里插入图片描述

**注意:**性能优化就是根据运行时的真实情况对各个资源点进行权衡折中的过程。锁粗化的思想和减少锁持有时间是相反的,在不同的场合,它们的效果不同,因此要根据实际情况进行取舍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/691673.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS-基础-MDN文档学习笔记

CSS构建基础 查看更多学习笔记:GitHub:LoveEmiliaForever MDN中文官网 CSS选择器 选择器是什么 CSS 选择器是 CSS 规则的第一部分,它用来选择HTML元素,选择器所选择的元素,叫做选择器的对象 选择器列表 如果有多…

Android轻量级进程间通信Messenger源码分析

一. 概述 Android中比较有代表性的两大通信机制:1. 线程间Handler通信 2. 进程间Binder通信,本篇文章中我们在理解AIDL原理的基础上来解读一下Messenger的源代码, 并结合示例Demo加深理解。 在看本篇文章前,建议先查阅一下笔者的…

举例说明什么是人机耦合

在呼叫中心行业,人机耦合是指将计算机自动化技术与人工服务相结合,以提高呼叫中心的效率和服务质量。具体来说,它包括通过智能语音识别、自然语言处理、机器学习等技术实现自动应答、自动导航、自动响应等功能,以及将人工客服与智…

【C++】类与对象(构造函数、析构函数、拷贝构造函数、常引用)

🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343🔥 系列专栏:http://t.csdnimg.cn/eCa5z 目录 类的6个默认成员函数 构造函数 特性 析构函数 特性 析构的顺序 拷贝构造函数 特性 常引用 前言 &…

力扣94 二叉树的中序遍历 (Java版本) 递归、非递归

文章目录 题目描述递归解法非递归解法 题目描述 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 示例 1: 输入:root [1,null,2,3] 输出:[1,3,2] 示例 2: 输入:root [] 输出:[] 示…

微信小程序-绑定数据并在后台获取它

如图 遍历列表的过程中需要绑定数据&#xff0c;点击时候需要绑定数据 这里是源代码 <block wx:for"{{productList}}" wx:key"productId"><view class"product-item" bindtap"handleProductClick" data-product-id"{{i…

Vue3实现带动画效果的tab栏切换

效果图如下所示&#xff1a; 实现思路&#xff1a; 其实很简单 1、首先切换tab栏时tab标签激活下标与对应显示内容下标要一致。 2、其次点击tab栏切换时更新下标 3、最后就是css添加动画效果 这样就了&#xff01;&#xff01;&#xff01; 上全部代码 <template><…

Profibus转ModbusRS485网关在空调系统应用

随着我国工业自动化整体水平的不断提高&#xff0c;企业中的控制系统和控制设备的种类越来越多&#xff1b;同时随着市场经济的发展&#xff0c;各个企业也对DCS系统能将控制系统的各个运行参数实时传送到上位机的系统中进行加工处理&#xff0c;这对DCS系统提出了通讯问题。开…

《Solidity 简易速速上手小册》第5章:智能合约的安全性(2024 最新版)

文章目录 5.1 安全性的重要性5.1.1 基础知识解析深入理解安全性的多维度影响智能合约安全的关键要素 5.1.2 重点案例&#xff1a;防止重入攻击案例 Demo&#xff1a;构建一个防重入的提款合约案例代码WithdrawContract.sol 测试和验证拓展功能 5.1.3 拓展案例 1&#xff1a;预防…

Day50 739每日温度 496下一个更大元素I 503下一个更大元素II

739 每日温度 请根据每日 气温 列表&#xff0c;重新生成一个列表。对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, 7…

面试经典150题——生命游戏

​"Push yourself, because no one else is going to do it for you." - Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 之所以先暴力求解&#xff0c;是因为我开始也没什么更好的思路&#xff0c;所以就先写一种解决方案&#xff0c;没准写着写…

22-k8s中pod的调度-亲和性affinity

一、概述 在k8s当中&#xff0c;“亲和性”分为三种&#xff0c;节点亲和性、pod亲和性、pod反亲和性&#xff1b; 亲和性分类名称解释说明nodeAffinity节点亲和性通过【节点】标签匹配&#xff0c;用于控制pod调度到哪些node节点上&#xff0c;以及不能调度到哪些node节点上&…

Linux-ls命令

目录 ls&#xff1a;查看目录下文件/文件夹 ls -l&#xff1a;列表显示文件 ls -a&#xff1a;显示所有文件正常情况下‘ . ’开头的文件是隐藏的 ls -la&#xff1a;以列表形式显示所有文件包括隐藏文件 ls -lt&#xff1a;按时间倒序查看文件 ls -R&#xff1a;递归方式…

【git 使用】超级好用的 git reset 和 git revert 功能对比和使用方法

首先你要知道 git 区分暂存区和工作区&#xff0c;如果你用过 sourcetree 你就会知道 git reset 超级好用 git reset 命令用于将当前分支的 HEAD 指针移动到指定的提交&#xff0c;并且可以选择性地修改工作区和暂存区的状态。git reset 命令有几种常用的用法&#xff0c;主要…

突破百度地图Web API的配额限制,实现接口调用自由!

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 引言 好久没用百度地图开放平台,最近发现平台调整了接口调用的策略,增加了实名认证,…

STM32 USART详细解读(理论知识)

文章目录 前言一、同步传输和异步传输二、UART协议三、UART硬件结构1.波特率&#xff0c;数据位&#xff0c;校验位&#xff0c;停止位设置2.数据发送流程3.数据接收流程4.中断控制 总结 前言 本篇文章来给大家讲解一下STM32中的USART&#xff0c;USART是STM32中非常重要的一个…

QT多线程应用及代码示例

一.多线程的原理和功能 1.多线程&#xff08;multithreading&#xff09;是指从软件或者硬件上实现多个线程并发执行的技术。 2.多线程的功能和作用主要包括&#xff1a; 提高程序的并发性和效率&#xff1a;多线程可以同时执行多个任务&#xff0c;不同的线程可以同时读写不…

腾讯云助力酒店IT系统上云,实现出海业务的双重优势

潮起潮涌&#xff0c;随着时代浪潮的翻涌&#xff0c;生活处处可见是巨大的变化&#xff0c;衣食住行都有了更多更大的需求&#xff0c;出门旅游观赏当地风景品尝特色美食的前提是要住好&#xff0c;只有休息好了才有更多的精力去游玩。酒店系统的升级上云让登记变得更加便捷&a…

【机器学习笔记】13 降维

降维概述 维数灾难 维数灾难(Curse of Dimensionality)&#xff1a;通常是指在涉及到向量的计算的问题中&#xff0c;随着维数的增加&#xff0c;计算量呈指数倍增长的一种现象。在很多机器学习问题中&#xff0c;训练集中的每条数据经常伴随着上千、甚至上万个特征。要处理这…

【Linux】git操作 - gitee

1.使用 git 命令行 安装 git yum install git 2.使用gitee 注册账户 工作台 - Gitee.com 进入gitee&#xff0c;根据提示注册并登录 新建仓库 仓库名称仓库简介初始换仓库 3.Linux-git操作 进入仓库&#xff0c;选择“克隆/下载” 复制下面的两行命令进行git配置 然后将仓库clo…