写在前面的话
本章讨论的是如何提高性能可伸缩性的问题,主要讨论的方法在于减少获取锁的时间,减少锁的粒度,减少锁占用的时间,或者用非独占或非阻塞锁来取代独占锁。
整体内容大部分都是知晓的,只是没有这么系统性去归纳和了解。
这几周相对会较忙,不过还是希望这个blog会继续更新下去
性能和可伸缩性
对于程序来说,首先要保证它正确,再确保它在正确的前提下能跑得更快,本章关注一些用于并发程序性能的分析,监测和改进的技术
性能的思考
- 更有效的利用我们现有的处理资源
- 让我们的程序尽可能的开拓更多可用的处理资源
性能遭遇“可伸缩性”
当增加计算资源时,吞吐量和生产量能够得到相应的改进
对性能的权衡进行评估
避免不成熟的优化,首先使程序变得正确,然后再加快,如果它运行的还不够快
决定一个方案更快前,先思考:
- 你所谓的快是什么
- 在什么样的条件下你的方案能真正的运行更快?轻负载还是重负载?大数据集还是小数据集
- 这些条件在你环境中发生的频率?
- 这些代码在其他环境不同条件下可能用到的情况?
- 用什么样的隐含代价,提高了风险?
Amdahl定律
1 | Speedup <= 1/(F + (1 - F) / N) |
- F:必须要串行化程序的比例
- N:CPU数量
所有的并发程序都有一些串行源
线程引入的开销
切换上下文
如果运行的线程大于目前的CPU数,那么OS会强行换出目前正在运行的程序,从而使其它线程能够利用CPU,这会引起上下文切换,会保存当前运行线程的上下文,并重新调入线程的执行上下文
内存同步
synchronized和volatie提供可见性的前提是使用一个memory barrier的指令来刷新缓存,使缓存无效
阻塞
锁为竞争性时,一定会引起阻塞
减少锁的竞争
并发程序中,对可伸缩性的首要威胁是独占的资源锁
用三种方式来减少锁的竞争
- 减少持有锁的时间
- 减少请求锁的频率
- 用协调机制替代独占锁,允许更强的并发性
缩小锁的范围
核心理念就是减少串行化代码的比例
不过在某些时刻缩小锁的范围,由于并行化开销的存在,会可能对性能出现反效果
减小锁的粒度
使用相互独立的锁来守卫多个独立的状态
分离锁
具体可以见concurrentHashMap的实现
避免热点域
例如计算一些统计信息时,为了避免每次请求都要计算,因此会引入缓存常用的计算结果,这些就会成为“热点域”
在concurrentHashMap中,通过枚举每个条目来得到size,避免了全局的热点域
独占锁的替代方法
包括:并发容器,读写锁,不可变对象,原子变量等
检测CPU使用率
通常,CPU没有被高效利用的原因是:
- 不充足的负载
- I/O限制
- 外部限制
- 锁竞争