笔记_并发编程实践_十二

来源:互联网 发布:php高端培训 编辑:程序博客网 时间:2024/06/06 09:45
测试并发程序


1.对比顺序程序,不同的并发程序存在不同程度的不确定性。那些潜在的错误的发生并不具有确定性,所以与不同的顺序测试相比,一定要有更广泛的覆盖度和更长的运行时间

2.主要包括安全性和活跃度的测试(与活跃度相关的测试时性能测试:吞吐量、响应性、可伸缩性)

3.在你的测试套件中包含一个顺序化的测试集,这些测试集可能在错误还未有设计并发问题时就发现他们

4.测试阻塞操作

(1).需要明确一点:测试是否通过的下信息,以及失败信息是否能够呈现出来,以供诊断问题使用

(2)当方法被成功阻塞后,必须想办法解除阻塞。如中断方法(必须该方法是响应中断的)

(3)确定线程是否已经阻塞,很难实现,可对非阻塞情况下执行的时间作一个估算,并是用一个更长的时间作测试的等待时间(必须随时准备调长,因为过短的估算会导致测试的假失败)
(4)注意:是用Thread.getState方法验证线程是否在一个条件等待下被真正阻塞并不可靠,线程并不需要真正被阻塞,线程只需要进入waiting或者time_waiting状态(如等待cpu分配时间片而不是等待锁?)

5.测试安全性
(1)开发良好的并发测试程序,可能比开发他们要去测试的类还要困难,最好能做到在检查测试的属性时,不要任何的同步。

(2)使用顺序或非顺序检验求和函数(不一定就是加法的求和),在程序中嵌入执行该函数的执行,并将最后的多个结果组合检验(应该满足交换率的操作,加法,异或等)(书中提到的不要让编译器预知该函数的结果的问题,大概是使用恒定的参数的话,该函数结果编译器甚至会提前计算出来,这里不太懂)

(3)可参照这里使用barrier控制多个线程在同一时间开始,并在他们都结束后同一获取结果的用法。

6.测试资源管理

(1)任何管理着其他对象的对象,都应该在不需要某个对象时,放弃该对象的引用,使得垃圾回收器回收内存等资源。(需要显示置空的情况不多,大多数情况下这种做法不仅无益,甚至有害。)

(2)通过堆检查工具查看资源的消耗

7.使用回调

(1)回调常用来对一个对象的生命周期的已知点上,来断言不变性约束

(2)通过使用自定义的线程工厂,我们可以对创建的线程施加更多的控制,如当前已创建线程的数量,线程何时终结,是否遵守执行策略等等
(是不是可以在使用自定义的线程工厂来创建某些任务线程,使得需要测试时可以显示相应的信息,运行模式下又可以忽略测试信息相关代码)

8.产生更多的交替操作

(1)多处理器可以产生更多的交替行为,类似的,不同处理器,操作系统,处理器架构下的测试可以发现那些特定于执行环境的问题。

(2)使用Thread.yield球阀更多的上下文转换(为了使得测试环境切换到生产环境时不需要删除测试代码,使用aop工具)

9.测试性能
(1)性能测试中包含一些基本的功能测试,这一做法永远是值得的,能确保正在做性能测试的代码是正确的。

(2)某些合理的测试场景是很容易被发现的,几乎所有生产者消费者模式都会用到有限缓存。可使用一个基础类基于具体测试场景扩展

(3)性能测试的参数(线程数,缓存容量等),或结果依赖于平台特征
10测量响应性
1.公平的信号量使得差异性变好,但平均时间变差,非公平的信号量则相反,有点事提供更好的平均时间

11.垃圾回收
(1).通过两种策略来避免垃圾回收对你的结果带来的误差:
<1>确保测试运行的整个期间,垃圾回收不会执行。(jvm中加入-verbose:gc)
<2>确保执行测试期间垃圾回收运行多次(如在生产者消费者模式中设计相当数量的内存分配和垃圾回收操作,生产者分配对象,消费者使用并丢弃,只要运行测试的时间足够长,就会引发多次垃圾回收)
12.动态编译
(1)当一个类首次被加载后,jvm会以字节码的方式执行,如果一个方法执行得足够频繁,动态编译器最终会将它挑出来,转换成本机代码;当编译完成后,执行方式将由解释执行转换到直接执行。
(2)避免编译对你的结果产生的影响:让你的程序长时间运行(至少几分钟),这样编译过程和解释执行仅仅占了总体运行时间的很小一部分。(hotspot jvm中加啊如-XX:+PrintCompolation)那么程序会在动态编译运行时打印出信息
13.代码路径的非真实取样
(1)jvm使用收集到的信息来帮助优化已经编译的代码,你的测试程序不仅应该尽量地接近一个典型的应用程序的使用模式,还应该尽量覆盖这个应用程序会用到的代码路径的集合,否则在当线程化的测试程序中,jvm会进行一些专门的优化,因此,即使你只想测量单线程的性能,也应该与多线程的性能测试结合到一起
14.不切实际的竞争程度
(1)为了获得有实际意义的结果,并发性能测试,除了需要考虑协调并发的因素,应该尽量去模拟线程本地的计算由某一个特有的应用程序来完成(如对锁的竞争程度不同,那么测试得到的性能瓶颈应该不同,如果不结合实际的应用程序,得到的测试将是无效的)
15.死代码的消除
(1)优化过的编译器擅长发现并遗弃死代码(这些代码不会对结果产生任何影响,编译器会从程序中删掉),对于某些测试情况可能使得测试结果无效
(2)-server模式的jvm可以产生更有效的代码,其中一个原因是它更擅长删除死代码,这样更容易出现上述的测试无效,但是,无论是测试模式还是生产环境,都应该使用server模式的jvm
(3)编写有效的性能测试,就需要哄骗优化器不要把你的基准测试当作死代码而优化,这需要每一个的计算结果都要应用在你的程序中——以一种不需要的同步或真实计算的方式。(如打印一行值去确保哪一些计算结果被用到(在测试的实际运行中做i/o操作不是一个好的选择,会破坏运行时间的测量)
(4)一个小技巧:
if(foo.x.hashCode()==System.nanoTime()){
System.out.print(" ");
}
     执行这样一段代码,即使该对象的hashCode罕见地等于系统当前时间,会执行print,由于print方法缓存输出,直到执行println时才会输出,所以并没有真正地执行io。
(5)计算结果应该不能被编译器所预测的,任何静态数据作为输入的测试程序都会受到这项优化的干扰(动态编译会用预计算的结果取代计算过程)
16.静态测试工具
(1)如findbugs的静态测试工具,能够发现静态代码中符合一定模式的bug

0 0