并发测试分为两部分
安全测试的定义:不发生任何错误的行为
活跃测试的定义:某个良好的行为终究会发生
与活跃性测试相关的是性能测试。性能可以通过多个方面来衡量,包括:

  1. 吞吐量:指一组并发任务中已完成任务所占的比例。
  2. 响应性:只请求从发出到完成之间的时间(也称为延迟)
  3. 可伸缩性:指在增加更多资源的情况下(通常指CPU)
  4. 吞吐量(或者缓解短缺)的提升情况

    12.1 正确性测试 - SemaphoreBoundedBuffer

    接下来将构建一组测试用例来测试一个有界缓存。

    12.1.1 基本的单元 - TestBoundedBuffer

    最基本的单元测试类类似于在串行上下文中执行的测试,然后调用它的各个方法,并验证它的后验条件和不变性条件。比如:新建立的缓存应该是空的,而不是满的。将N个元素插入到容量为N的缓存中,然后测试缓存是否已经填满。

    12.1.2 对阻塞操作的测试

    Thread.getState的返回结果不能用于并发控制,它将限制测试的有效性—其主要作用还是作为调试信息的来源。 JSR 166 专家组创建了一个基类,定义了一些方法可以在testDown期间传递和报告失败信息,并遵循一个约定:每个测试必须等待它所创建的全部线程结束以后才能完成。
    http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/test/tck/JSR166TestCase.java

12.1.3 安全性测试 - XorShiftPutTakeTest

在构建对并发类的安全测试中,需要解决的关键问题在于,要找出那些容易检查的属性,这些属性在发送错误的情况下极有可能失败,同时又不会使得错误检查代码人为地限制并发性。理想情况是,在测试属性中不需要任何同步机制。

12.1.4 资源管理的测试 - TestBoundedBuffer

之所以要限制缓存的大小,其原因就是要防止由于资源耗尽而导致应用程序发送故障,例如生产者速度远远高于消费者的处理速度。

12.1.5 使用回调 - TestThreadPool

12.1.6 产生更多的交替操作

并发代码的大部分错误都是低概率事件,所以需要反复执行多次。 如果处理器的数量少于活动线程的数量,将产生交替行为,可以使用Thread.yield产生更多的上下文切换(这项技术的有效性与具体的平台相关)

  1. public synchronized void transferCredits(Account from,
  2. Account to,
  3. int amount) {
  4. from.setBalance(from.getBalance() - amount);
  5. if (random.nextInt(1000) > THRESHOLD) {
  6. Thread.yield();
  7. }
  8. to.SetBalance(to.getBalance() + amount);
  9. }
  1. 程序清单 12-10 使用Thread.yield 来产生更多的交替操作 <br />通过在某个操作的执行过程中调用yield方法,可以将这些错误暴露出来。这需要在测试中添加一些调用并且在正式产品中删除这些调用,这带来了不便性,可以通过AOP,降低这种不便性。

12.2 性能测试

性能测试通常是功能测试的延伸。性能测试将衡量典型测试用例中的端到端性能。
在生产这-消费者设计中通常都会用到有界缓存,因此显然需要测试生产者在向消费者提供数据时的吞吐量。对PutTakeTest进行扩展,使其成为针对该场景的性能测试。
性能测试第二个目标是根据经验值来调整各种不同的限制,例如线程数量、缓存容量等。

12.2.1 在PutTakeTest中增加计时功能

CyclicBarrier知识点:他有两个构造函数,在两个参数的构造函数中,有参数barrierAction,这是一个runnable,当栅栏tripped后触发Runnable的run。

12.2.2 多种算法的比较

12.2.3 响应性衡量

12.3 避免性能测试的陷阱

12.3.1 垃圾回收

有两种策略可以防止垃圾回收操作对测试结果产生偏差
1、确保垃圾回收操作在测试运行的整个期间都不会执行(可以在调用JVM是指定 -verbose: gc来判断是否执行了垃圾回收操作)
2、确保垃圾回收操作在测试期间执行多次,这样测试程序就能充分放映出运行期间的内存分配与垃圾回收等开销。 通常第二策略更好,它要求跟车的测试时间,并且更有可能放映实际环境下的性能。

12.3.2 动态编译

应该防止动态编译对测试结果造成影响,有两种方法:
1、是程序运行足够长的时间(至少数分钟),这样编译过程以及解释执行都只是总运行时间的很小一部分。 2、使代码预先运行一段时间并且不测试这段时间内的代码性能,这样在开始计时前代码就以及完全编译了。 * 在运行程序时使用命令行选项 -xx: +PrintCompilation,那么当动态编译运行时将输出一条信息,而不是在运行过程中执行。

12.3.5 无用代码的消除

无论在正式产品还是测试版本中,都应该选择 -server模式而不是-client模式—只是在测试时必须保证它们不会受到无用代码消除优化的影响。 要编写有效的性能测试程序,就需要告诉优化器不要将基准测试当做无用代码而优化掉,这就要要求在程序中对每个计算结果都要通过某种方式来使用,这种方式不需要同步或者大量代码。

12.4 其他的测试方法