《java并发编程实战》 第十二章 并发程序的测试

第十二章 并发程序的测试

  测试并发程序而言,所面临的主要挑战在于:潜在的错误发生具有不确定性,需要比普通的串行程序测试更广的范围并且执行更长的时间。
并发测试大致分为两类:安全性测试和活跃性测试。
  安全测试 ----- 通常采用测试不变性条件的形式,即判断某个类的行为是否与其他规范保持一致。
  活跃性测试 ----- 包括进展测试和无进展测试两个方面(很难量化)。
  性能测试----- 性能测试与活跃性测试相关,主要通过:吞吐量、响应性、可伸缩性衡量。

正确性测试

测试并发类设计单元测试时,首先要执行与测试串行类时相同的分析----找出需要检查的不变性条件与后验条件。(不变性条件:判断状态是有效还是无效,后验条件:判断状态改变后是否有效)。接下来讲通过构建一个基于Semaphore来实现的缓存的有界缓存,测试缓存的正确性。

基本单元测试(基于信号量有界缓存BoundedBuffer例子)

  知识铺垫(Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”)
  BoundedBuffer 用一个泛型数组、Semaphore 实现了一个固定长度的、可以缓存队列可删除可插入个数的队列。availableItems表示可以从缓存中删除的元素个数。availableSpaces表示可以插入到缓存的元素个数,初始值等于缓存的大小。分析:
  availableItems 设置为0,要求任何线程在accquire之前要release保证了队列必须插入有值才能take
  availableSpaces 初始值为大小为capacity,表明队列最大值为capacity,同时也表明一开始最多有capacity个线程可以同时put队列,当然随着队列插入可同时插入的线程变少。
  在最关键的插入、取出队列的操作中,采用synchronized 包装两个方法,保证了同步性。

说明:在实际使用中,肯定不可能自己编写一个有界缓存,但是此例实现的思路值得学习。如果实际需要使用有界缓存,应该直接使用ArrayBlockingQueue或者LinkedBlockingQueue。

public class BoundedBuffer<E> {
   
    //可用信号量、空间信号量
    private final Semaphore availableItems, availableSpaces;
    private final E[] items;//缓存
    private int putPosition = 0, takePosition = 0;//放、取索引位置
    public BoundedBuffer(int capacity) {
   
           availableItems = new Semaphore(0);//初始时没有可用的元素
           availableSpaces = new Semaphore(capacity);//初始时空间信号量为最大容量
           items = (E[]) new Object[capacity];
    }
    public boolean isEmpty() {
   
           //如果可用信号量为0,则表示缓存为空
           return availableItems.availablePermits() == 0;
    }
    public boolean isFull() {
   
           //如果空间信号量为0,表示缓存已满
           return availableSpaces.availablePermits() == 0;
    }
    public void put(E x) throws InterruptedException {
   
           availableSpaces.acquire();//阻塞获取空间信号量
           doInsert(x);
           availableItems.release();//可用信号量加1
    }
    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] = x;
           putPosition = (++i == items.length) ? 0 : i;
    }
    private synchronized E doExtract() {
   
           int i = takePosition;
           E x = items[i];
           items[i] = null;//加快垃圾回收
           takePosition = (++i == items.length) ? 0 : i;
           return x;
    }
}

先进行基本的单元测试:该基本的单元测试相当于串行上下文中执行的测试,测试了BoundedBuffer的所有方法,间接验证其后验条件和不变性条件。

public class BoundedBufferTest extends TestCase {
   
  //刚构造好的缓存是否为空测试
  public void testIsEmptyWhenConstructed() {
   
         BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
         assertTrue(bb.isEmpty());
         assertFalse(bb.isFull());
  }
  //测试是否满
  public void testIsFullAfterPuts() throws InterruptedException {
   
         BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
         for (int i = 0; i < 10; i++)
                bb.put(i);
         assertTrue(bb.isFull());
         assertFalse(bb.isEmpty());
  }

对阻塞行为与对中断响应的测试

  在测试并发的基本属性时,需要引入多个线程。大多数测试框架并不能很好的支持并发性测试,测试阻塞行为,当然线程被阻塞不再执行时,阻塞才是成功的,为了让阻塞行为效果更明显,可以在阻塞方法中抛出异常。
  当阻塞发生后,要使方法解除阻塞最简单的方式是采用中断,可以在阻塞方法发生后,线程阻塞后再中断它,当然这要求阻塞方法提取返回或者抛出InterrupedException来响应中断。

  例如BoundedBuffer的阻塞行为以及对中断的响应性测试,如果从空缓存中获取一个元素,如果take方法成功,表明测试失败。在等待“获取”一段时间后,再中断该线程,如果线程在调用Object类的wait()、 join() 或者sleep()方法被强行中断,那么将会抛出InterruptedException。

  public class TestBoundedBufferBlock extends TestCase {
   
   public void testTakeBlocksWhenEmpty() {
   
          final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值