《实战 Java 高并发程序设计》笔记——第4章 锁的优化及注意事项(一)

本文是学习《实战 Java 高并发程序设计》的笔记,主要探讨了提高锁性能的建议,包括减小锁持有时间、减小锁粒度、使用读写分离锁、锁分离和锁粗化。Java虚拟机通过锁偏向、轻量级锁、自旋锁和锁消除等手段优化锁的性能。此外,文章还介绍了ThreadLocal的使用和原理,强调了它如何提供线程安全的局部变量,并探讨了其对性能的影响和内存管理问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

声明:

本博客是本人在学习《实战 Java 高并发程序设计》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。

本博客已标明出处,如有侵权请告知,马上删除。

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

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

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

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

4.1 有助于提高 “锁” 性能的几点建议

“锁” 的竞争必然会导致程序的整体性能下降。为了将这种副作用降到最低,我这里提出一些关于使用锁的建议,希望可以帮助大家写出性能更为优越的程序

4.1.1 减小锁持有时间

对于使用锁进行并发控制的应用程序而言,在锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系。如果线程持有锁的时间很长,那么相对地,锁的竞争程度也就越激烈。可以想象一下,如果要求 100 个人各自填写自己的身份信息,但是只给他们一支笔。那么如果每个人拿着笔的时间都很长,总体所花的时间就会很长。如果真的只能有一支笔共享给 100 个人用,那么最好就让每个人花尽量少的时间持笔,务必做到想好了再拿笔写,千万不可拿着笔才去思考这表格应该怎么填。程序开发也是类似的,应该尽可能地减少对某个锁的占有时间,以减少线程间互斥的可能。以下面的代码段为例:

在这里插入图片描述

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

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

在这里插入图片描述

在改进的代码中,只针对 mutextMethod() 方法做了同步,锁占用的时间相对较短,因此能有更高的并行度。这种技术手段在 JDK 的源码包中也可以很容易地找到,比如处理正则表达式的 Pattern 类:

在这里插入图片描述

matcher() 方法有条件地进行锁申请,只有在表达式未编译时,进行局部的加锁。这种处理方式大大提高了 matcher() 方法的执行效率和可靠性。

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

4.1.2 减小锁粒度

减小锁粒度也是一种削弱多线程锁竞争的有效手段。这种技术典型的使用场景就是 ConcurrentHashMap 类的实现。大家应该还记得这个类吧!在 “3.3 JDK的 并发容器” 一节中,我向大家介绍了这个高性能的 HashMap。但是当时我们并没有说明它的实现原理。这里,让我们更加细致地看一下这个类。

对于 HashMap 来说,最重要的两个方法就是 get() 和 put() 。一种最自然的想法就是对整个 HashMap 加锁,必然可以得到一个线程安全的对象。但是这样做,我们就认为加锁粒度太大。对于 ConcurrentHashMap,它内部进一步细分了若干个小的 HashMap,称之为段(SEGMENT)。默认情况下,一个 ConcurrentHashMap 被进一步细分为 16 个段

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

由于默认有 16 个段,因此,如果够幸运的话,ConcurrentHashMap 可以同时接受 16 个线程同时插入(如果都插入不同的段中),从而大大提供其吞吐量。下面代码显示了 put() 操作的过程。在第 5~6 行,根据 key,获得对应的段的序号。接着在第 9 行,得到段,然后将数据插入给定的段中。

在这里插入图片描述

但是,减少锁粒度会引入一个新的问题,即:当系统需要取得全局锁时,其消耗的资源会比较多。仍然以 ConcurrentHashMap 类为例,虽然其 put() 方法很好地分离了锁,但是当试图访问 ConcurrentHashMap 全局信息时,就会需要同时取得所有段的锁方能顺利实施。比如 ConcurrentHashMap 的 size() 方法,它将返回 ConcurrentHashMap 的有效表项的数量,即 ConcurrentHa

内容概要:本文详细介绍了基于FPGA的144输出通道可切换电压源系统的设计与实现,涵盖系统总体架构、FPGA硬件设计、上位机软件设计以及系统集成方案。系统由上位机控制软件(PC端)、FPGA控制核心和高压输出模块(144通道)三部分组成。FPGA硬件设计部分详细描述了Verilog代码实现,包括PWM生成模块、UART通信模块和温度监控模块。硬件设计说明中提及了FPGA选型、PWM生成方式、通信接口、高压输出模块和保护电路的设计要点。上位机软件采用Python编写,实现了设备连接、命令发送、序列控制等功能,并提供了个图形用户界面(GUI)用于方便的操作和配置。 适合人群:具备定硬件设计和编程基础的电子工程师、FPGA开发者及科研人员。 使用场景及目标:①适用于需要精确控制多通道电压输出的实验环境或工业应用场景;②帮助用户理解和掌握FPGA在复杂控制系统中的应用,包括PWM控制、UART通信及多通道信号处理;③为研究人员提供个可扩展的平台,用于测试和验证不同的电压源控制算法和策略。 阅读建议:由于涉及硬件和软件两方面的内容,建议读者先熟悉FPGA基础知识和Verilog语言,同时具备定的Python编程经验。在阅读过程中,应结合硬件电路图和代码注释,逐步理解系统的各个组成部分及其相互关系。此外,实际动手搭建和调试该系统将有助于加深对整个设计的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值