Java并发学习之并发程序的测试 一
一.概述
在编写并发程序时,可以采用与编写串行程序时相同的设计原则与设计模式。 二者的差异在于,并发程序存在一定程度的不确定性,而串行程序中不存在这个问题。
所以在测试并发程序时,所面临的主要挑战在于:潜在错误的发生并不具有确定性,而是随机的。 要在测试中将这些故障暴露出来,就需要比普通的串行程序测试覆盖更广的范围并且执行更长的时间。
并发测试大致分为两类: 1)安全性测试 2)活跃性测试 。 在前面的学习中我们将安全性定义为:不发生任何错误的行为。 将活跃性定义为:某个良好的行为终究会发生。
1.安全性测试
在进行安全性测试时,通常或采用测试不变性条件的形式,即判断某个类的行为是否与其规范保持一致。
例如假设我们现在有一个链表,在它每次被修改时把其大小缓存下来,那么其中一项安全性测试中就是比较在缓存中保存的大小值与链表中实际元素的数目是否相等。 这种测试在单线程程序中很简单,因为在测试时链表的内容不会发生变化。 但在多线程的并发程序中,这种测试将可能由于竞争而失败,除非能将访问计数器的操作和统计元素数据的操作合并为单个原子操作,而要实现这一点我们可以对链表加锁以实现独占访问,然后采用链表中提供的某种 “原子快照” 功能,或者在某些 “测试点” 上采用原子方式来判断不变性条件或者执行测试代码。 测试程序将努力在足够大的状态空间中查找出错的地方。 然而,测试代码同样会对执行时序或者同步操作带来影响(这些影响可能会掩盖一些本可以暴露的错误)。
2.活跃性测试
同样测试活跃性本身也存在问题。 活跃性测试包括进展测试和无法进展测试两方面,这些都是很难量化的(如何验证某个方法是被阻塞了,而不是运行缓慢?,如何测试某个算法不会发生死锁? 要等待多久才能宣告它发生了故障?)
与活跃性测试相关的是性能测试。
性能可以通过多个方面来衡量,,包括:
1)吞吐量。 指一组并发任务中已经完成任务所占的比例。
2)响应性。 指请求从发出到完成之间的时间。
3)可伸缩性。 指在增加更多资源的情况下,吞吐量的提升情况。
二.具体学习
1.正确性测试
在对某个并发类设计单元测试时,首先需要执行与测试串行类时相同的分析(找出需要检查的不变性条件和后验条件)。 如果幸运的话,在类的规范中将给出其中大部分的条件,而在剩下的时间里,当编写测试时将不断地发现新的规范。
为了进一步学习,我们已一个有界缓存的类为例进行深入学习:
//基于信号量的有界缓存
public class BoundedBuffer<E>{
private final Semaphore availableItems, availableSpaces;//声明信号量
private final E[] items;//元素存放数组
private int putPosition = 0 ,takePosition = 0;//索引变量
//构造方法,初始化信号量
BoundedBuffer(int capacity) {
availableItems = new Semaphore(0);//用于标识可利用的元素数量
availableSpaces = new Semaphore(capacity);//用于标识剩余空间
items = (E[]) new Object[capacity];//分配内存
}
//判断缓存区是否为空,实际上判断可利用元素是否为0即可
public boolean isEmpty(){
return availableItems.availablePermits()==0;
}
//判断缓存区是否满了,实际上判断剩余空间为0即可
public boolean isFull(){
return availableSpaces.availablePermits()==0;
}
//存入元素
public void put(E x) throws InterruptedException{
availableSpaces.acquire();
doInsert(x);
availableItems.release();
}
//拿出元素
public E take() throws InterruptedException{
availableItems.acquire();
E item = doExtract();
availableSpaces.release();
return item;
}
//存入元素
private synchronized void doInsert(E x){
int i=putPosition;//记录存入的位置
items[i]

浙公网安备 33010602011771号