Java并发编程(十)

写在前面的话

本章讨论的是如何提高性能可伸缩性的问题,主要讨论的方法在于减少获取锁的时间,减少锁的粒度,减少锁占用的时间,或者用非独占或非阻塞锁来取代独占锁。

整体内容大部分都是知晓的,只是没有这么系统性去归纳和了解。

这几周相对会较忙,不过还是希望这个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限制
  • 外部限制
  • 锁竞争