Java高并发——性能与可伸缩性

本文深入探讨了线程性能和可伸缩性的重要性,详细分析了Amdahl定律,介绍了如何减少锁竞争和上下文切换开销的方法,包括锁分解、锁分段、使用ReadWriteLock和原子变量等策略。

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

一、性能和可伸缩性

线程最主要的目的是提高系统的运行性能。
始终要把安全性放在第一位,首先保证线程的安全性,只有在程序有性能要求时,才进行进一步的性能优化。

1 性能与可伸缩性

对于服务器应用程序来说,“多少”这个方面比“多块”这个方面更受重视。
当进行决策时,有时候会通过增加某种形式的成本来降低另一种形式的开销。

1.1 性能

当进行性能调优时,其目的是用更小的代价完成相同的工作。

1.1.1 性能优化

要提高应用的性能,主要有两方面:

  1. 有效利用现有的处理资源
  2. 在出现新的处理资源时使系统尽可能的利用这些资源。

1.1.2 指标

程序的性能可以用多个指标衡量:

  1. 程序运行速度
    服务时间,响应时间,等待时间
  2. 程序处理能力
    生产量,吞吐量

1.2 可伸缩性

可伸缩性指,当增加计算资源时(CPU、内存、IO、带宽)时,程序的吞吐量或处理能力能相应的增加。

1.2.1 可伸缩性优化

目的是怎么将问题并行化。

2 Amdahl定律

在增加计算资源的情况下,程序在理论上能够实现最高的加速比,这个值取决于程序中可并行组件与串行组件所占的比例。
在所有的并发程序中都包含一些串行部分。

3 线程引入的开销

并行带来的性能提升必须超过并发导致的开销。

3.1 上下文切换

  1. 上下文切换的开销
  2. 缓存丢失的开销
    当线程频繁的发生阻塞,那么它们将无法使用完整的调度时间片。

3.2 内存同步

内存同步一般都会使用一些特殊的命令,即内存栅栏。内存栅栏可以刷新缓存,使缓存无效,刷新硬件的写缓存,以及停止执行管道,它将抑制编译器及硬件的优化操作。
同步会加大内存总线上的通信量。

3.3 阻塞

竞争的同步需要操作系统的介入,增加开销。

  1. 自旋等待
    通过循环的尝试获取锁,直到成功
  2. 操作系统挂起
    由操作系统将线程移到等待队列。

4 减少锁竞争

在并发程序中,对可伸缩性的最主要的威胁就是独占方式的资源锁。

4.1 原则

原则:

  1. 降低锁的获取频率
  2. 降低持有锁的时间
    如果两者的乘积很小,那么大多数锁的操作都不会发生竞争,因此在该锁上的竞争不会对可伸缩性造成严重的影响。
  3. 使用带有协调机制的独占锁

4.2 策略

4.2.1 缩小锁的范围

尽可能的缩短锁持有的时间。

4.2.2 减少锁的粒度

减少锁的请求频率,这可以通过锁分解和锁分段等技术实现,在这些技术中将采用多个相互独立的锁来保护独立的状态变量,从而改变这些变量在之前由单个锁保护的情况。

4.2.2.1 锁分解

如果一个锁需要保护多个相互独立的状态变量,那么可以将这个锁分解为多个锁,并且每一个锁只保护一个变量,从而提供可伸缩性,最终降低每个锁被请求的频率。

4.2.2.2 锁分段

可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况被称为锁分段。
ConcurrentHashMap

4.2.3 避免热点域

4.2.4 替代独占锁的其他方法

4.2.4.1 ReadWriteLock

针对读多写少得情况进行了进一步的优化。

4.2.4.2 原子变量

可以降低热点域的开销

4.2.5 拒绝对象池

线程分配新的对象时,基本上不需要在线程间进行协调,直接从线程本地内存块进行分配即可。
通常,对象分配操作的开销比同步开销更低。

4.2.6 监控CPU利用率

  1. 负载不充足
  2. IO密集型
  3. 外边限制
  4. 锁竞争

5 减少上下文切换开销

日志记录的例子:
如果直接由日志的操作线程进行日志的记录,会执行写得IO操作,从而发生阻塞,进行一次线程上下文切换。
如果将IO操作从处理请求的线程中分离出来,处理线程只需将日志对象添加到队列中即可,从而避免线程切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值