Java教程

并发编程实战总结(11)

本文主要是介绍并发编程实战总结(11),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1,对于一个给定的操作,通常会缺乏某种特定的资源,例如CPU时钟周期、内存、网络带宽、I/O带宽、数据库请求、磁盘空间以及其他一些资源。当操作性能由于某种特定的资源而受到限制时,我们通常将该操作称为资源密集型的操作,例如CPU密集型、数据库密集型等。
2,市场上有一些成熟的剖析工具,用来评估性能,追踪性能瓶颈,但是你不必花费大量的金钱用于了解你的程序做了什么。例如,免费的perfbar应用程序可以给你一张相当不错的图表,告诉你CPU究竟是如何忙碌地工作,并且你的目标通常是保持CPU的忙碌。这便是一个很好的方式,使你能够评估你是否需要性能调节,或者你调节的效果如何。见P186
3,Amdahl定律:最高加速比为Speedup<=1/(F+(1-F)/N),F为必须被串行执行的部分,N为处理器个数,当N无穷大的时候,最大的加速比趋近于1/F,见P186
4,上下文切换:如果可运行的线程数大于CPU的数量,那么操作系统会把某个正在运行的线程调度出来,从而让新的线程能够使用CPU,这将导致一次上下文切换,在这个过程中,将保存正在运行线程的上下文,将新调度进来的线程执行上下文设置为当前上下文。
应用程序、操作系统以及JVM都使用一组相同的CPU。在JVM和操作系统的代码中消耗越多的CPU时钟周期,应用程序的可用CPU时钟周期就越少。但上下文切换的开销并不只是包含JVM和操作系统的开销。当一个新的线程被切换进来时,它所需的数据可能不在当前处理器的本地缓存中,因此上下文切换将导致一些缓存缺失,因而线程在首次调度运行时会更加缓慢。
当线程因为竞争一个锁而阻塞时,JVM通常会将这个线程挂起,允许它被换出去。如果线程频繁发生阻塞,那线程就不能使用完整的调度时间片。一个程序发生越多的阻塞(阻塞I/O,等待竞争锁,或者等待条件变量),与CPU密集型的程序就会发生越多的上下文切换,这增加了调度的开销,并减少了吞吐量。(无阻塞的算法能减小上下文切换;参见第15章。)
切换上下文真正的开销根据不同的平台而变化,但是按经验来看:在大多数通用的处理器中,上下文切换的开销相当于5000到10000个时钟周期,或者几微秒。Unix系统的vmstat命令和Windows系统的perfmon工具都能报告.上下文切换次数和内核占用的时间等信息。高内核占用率(超过10%)通常象征调度很频繁,这很可能是由I/O阻塞,或竞争锁引起的。P190
5,内存同步:同步操作的性能开销包括多个方面。在synchronized 和volatile提供的可见性保证中可能使用一些特殊指令,即内存栅栏(memory Barrier)。内存栅栏可以刷新缓存,使缓存无效,刷新硬件的写缓冲,以及停止执行管道。内存栅栏可能同样会带来间接的影响,因为它抑制了一些编译器的优化操作;在内存栅栏中,大多数操作是不能被重排序的。
在我们评估同步给性能带来影响的同时,区分竞争同步和无竞争同步也是非常重要的。synchronized机制对无竞争同步进行了优化(volatile总是非竞争的),在写作本书的时候,一个普通"fast-path" 的非竞争同步,其性能开销在20至250个时钟周期。虽然这个开销不为零,但是它产生的影响已经微乎其微了。另一种选择是对安全性进行妥协,把自己陷入痛苦地搜寻bug的行动来保证你( 或者你的后续事务)的安全。P190
6,阻塞:非竞争的同步可以由JVM进行处理(Bacon等,1998) ;而竞争的同步可能需要操作系统的介入,这会增大开销。当锁为竞争性的时候,失败的线程(一个或多个)必然发生阻塞。JVM既能自旋等待(spin-waiting, 不断尝试获取锁,直到成功),或者在操作系统中挂起(suspending)这个被阻塞的线程。哪个效率更高,取决于上下文切换的开销,以及成功地获取锁需要等待的时间。自旋等待更适合短期的等待,而挂起适合长时间等待。有一些JVM基于过去等待时间的数据剖析来在这两者之间进行选择,但是大多数等待锁的线程都是被挂起的。
有3种方式来减少锁的竞争:
●减少持有锁的时间;
●减少请求锁的频率;
●或者用协调机制取代独占锁,从而允许更强的并发性。(使用并发容器、读写锁、不可变对象以及原子变量)
7,缩小锁的范围(快件快出):尽量缩小同步代码块能提高可伸缩性,但同步块也不能过小——一些需要采用原子方式执行的操作(例如对某个不变性条件中的多个变量进行更新)必须包括在一个同步块中。此外,同步需要一定的开销,当把一个同步代码块分解为多个同步代码块时(在确保正确性的情况下),反而会对性能提升产生负面影响。在分解代码块时,理想的平衡点将与平台相关,但在实际情况下,仅当可将一些"大量"的计算或阻塞操作从同步代码块中移出时,才应该考虑同步代码块的大小
8,只有竞争明显才使用分段锁,过度会造成死锁风险。
9,如果采用锁分段技术,一定要表现出对锁的竞争频率大于对锁保护数据竞争的频率。如果一个锁守护两个独立变量X和Y,线程A想要访问X,而线程B想要访问Y (这就好像ServerStatus中一个线程调用了addUser,而另一个调用了adaQuery) ,这两个线程没有竞争任何数据,然而它们竞争相同的锁。
10,对象池:

这篇关于并发编程实战总结(11)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!