Java并发编程的性能和可伸缩性

Safe is first,performance is second.    

1、性能思考

使用多线程总会引入一些性能开销:与协调线程相关的开销(加锁、信号、内存同步),增加上下文切换,线程的创建和消亡,以及调度的开销。

可伸缩性的是当增加计算资源的时候(比如增加额外CPU数量、内存、存储器、I/O带宽),吞吐量和生产量能够相应地得以改进。

从性能的多个角度来看:“有多少”方面——可伸缩性,吞吐量和生产量,在Server应用程序中往往比“有多快”受到更多的关注。

避免不成熟的优化。首先使程序正确;如果它运行得还不够快,再加快。

在决定某个方案比其他方案“更快”之前,先问自己一些问题:

1)你所谓的更“快”指的什么?

2)在什么样的条件下你的方案能够真正运行的更快?在轻负载还是在重负载下?大数据集还是小数据集?是否支持你的测量标准的答案?

3)这些条件在你的环境中发生的频率?是否支持你的测量标准的答案?

4)这些代码在其他环境的不同条件下被用到的可能性?

5)你用什么隐含的代价,比如增加的开发风险或维护性,换取了性能的提高?这个权衡的决定是否正确?

对性能的追求很可能是并发bug唯一最大的来源。测评,不要臆测。

2、Amdahl定律

Amdahl定律描述了在一个系统中,基于可并行化和串行化的组件各自所占的比重,程序通过获得额外的计算资源,理论上能够加速多少。

如果F是必须串行化执行的比重,那么Amdahl定律告诉我们,在一个N处理器的机器中,我们最多可以加速:Speedup <= 1/(F+(1-F)/N)

所有并发程序都有一些串行源;如果你认为没有,那么去仔细检查吧。   

3、线程引入的开销及减少锁的竞争

不要过分担心非竞争的同步带来的开销。基础的机制已经足够快了,在这个基础上,JVM能够进行额外的优化,大大减少或消除了开销。关注那些真正发生了锁竞争的区域中性能的优化。   

并发程序中,对可伸缩性首要的威胁是独占的资源锁

有3种方式来减少锁的竞争:

  • 减少持有锁的时间(缩小锁的范围);
  • 减少请求锁的频率(分拆锁和分离锁);
  • 或者用协调机制取代独占锁,从而允许更强的并发性。

每个变化操作都要访问它(比如计算HashMap的计算器),称为热点域

不均匀的CPU利用率表明,大多数计算都由很小的线程集完成,你的应用程序将不能够利用额外的处理器资源。   

4、比较Map的性能

ConcurrentHashMap要比同步的HashMap的性能好。

ConcurrentHashMap和ConcurrentSkipListMap的数据显示,它们能很好地应对数量很大的线程;吞吐量随着线程数量的增加而增长。

5、减少上下文切换引起的开销

把I/O操作移到了另一个线程,使用户看不到这个开销;同样取消了输出流竞争,因此较少了竞争源。 



留言