并发测试分为两类:安全性测试(无论错误的行为不会发生)而活性测试(会发生)。

安全測试 - 通常採用測试不变性条件的形式,即推断某个类的行为是否与其它规范保持一致。

活跃性測试 - 包含进展測试和无进展測试两个方面。

性能測试与活跃性測试相关,主要包含:吞吐量、响应性、可伸缩性。

一、正确性測试

找出须要检查的不变条件和后延条件。
import java.util.concurrent.Semaphore;

public class BoundedBuffer<E> {

	private final Semaphore availableItems, availableSpaces;
private final E[] items;
private int putPosition = 0;
private int takePosition = 0; @SuppressWarnings("unchecked")
public BoundedBuffer(int capacity) {
availableItems = new Semaphore(0);
availableSpaces = new Semaphore(capacity);
items = (E[]) new Object[capacity];
} public boolean isEmpty() {
return availableItems.availablePermits() == 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] = 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;
} }

1 主要的单元測试

import static org.junit.Assert.*;
import org.junit.Test; public class BoundedBufferTests { @Test
public void testIsEmptyWhenConstructed() {
BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
assertTrue(bb.isEmpty());
assertFalse(bb.isFull());
} @Test
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());
assertTrue(bb.isEmpty());
}
}


2 对堵塞操作的測试
take方法是否堵塞、中断处理。从空缓存中获取一个元素。
	@Test
public void testTakeBlocksWhenEmpty(){
final BoundedBuffer<Integer> bb = new BoundedBuffer<Integer>(10);
Thread taker = new Thread(){
@Override
public void run() {
try {
int unused = bb.take();
fail(); //假设运行到这里。那么表示出现了一个错误
} catch (InterruptedException e) { }
}
};
try {
taker.start();
Thread.sleep(LOCKUP_DETECT_TIMEOUT);
taker.interrupt();
taker.join(LOCKUP_DETECT_TIMEOUT);
assertFalse(taker.isAlive());
} catch (InterruptedException e) {
fail();
}
}

创建一个“获取”线程,该线程将尝试从空缓存中获取一个元素。
假设take方法成功,那么表示測试失败。
运行測试的线程启动“获取”线程。等待一段时间,然后中断该线程。

假设“获取”线程正确地在take方法中堵塞。那么将抛出InterruptedException。而捕获到这个异常的catch块将把这个异常视为測试成功,并让线程退出。

然后,主測试线程会尝试与“获取”线程合并,通过调用Thread.isAlive来验证join方法是否成功返回,假设“获取”线程能够响应中断。那么join能非常快地完毕。


使用Thread.getState来验证线程是否能在一个条件等待上堵塞,但这样的方法并不可靠。

被堵塞线程并不须要进入WAITING或者TIMED_WAITING等状态,因此JVM能够选择通过自旋等待来实现堵塞。



3 安全性測试
在构建对并发类的安全性測试中,须要解决地关键性问题在于,要找出那些easy检查的属性,这些属性在错误发生的情况下极有可能失败,同一时候又不会使得错误检查代码人为地限制并发性。理想情况是,在測试属性中不须要不论什么同步机制。

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.TestCase; public class PutTakeTest extends TestCase {
private static final ExecutorService pool = Executors.newCachedThreadPool();
private final AtomicInteger putSum = new AtomicInteger(0);
private final AtomicInteger takeSum = new AtomicInteger(0);
private final CyclicBarrier barrier;
private final BoundedBuffer<Integer> bb;
private final int nTrials, nPairs; public static void main(String[] args) {
new PutTakeTest(10, 10, 100000).test(); // 演示样例參数
pool.shutdown();
} static int xorShift(int y) {
y ^= (y << 6);
y ^= (y >>> 21);
y ^= (y << 7);
return y;
} public PutTakeTest(int capacity, int nPairs, int nTrials) {
this.bb = new BoundedBuffer<Integer>(capacity);
this.nTrials = nTrials;
this.nPairs = nPairs;
this.barrier = new CyclicBarrier(nPairs * 2 + 1);
} void test() {
try {
for (int i = 0; i < nPairs; i++) {
pool.execute(new Producer());
pool.execute(new Consumer());
}
barrier.await(); // 等待全部的线程就绪
barrier.await(); // 等待全部的线程运行完毕
assertEquals(putSum.get(), takeSum.get());
} catch (Exception e) {
throw new RuntimeException(e);
}
} class Producer implements Runnable {
@Override
public void run() {
try {
int seed = (this.hashCode() ^ (int) System.nanoTime());
int sum = 0;
barrier.await();
for (int i = nTrials; i > 0; --i) {
bb.put(seed);
sum += seed;
seed = xorShift(seed);
}
putSum.getAndAdd(sum);
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} class Consumer implements Runnable {
@Override
public void run() {
try {
barrier.await();
int sum = 0;
for (int i = nTrials; i > 0; --i) {
sum += bb.take();
}
takeSum.getAndAdd(sum);
barrier.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

4 资源管理的測试
对于不论什么持有或管理其它对象的对象,都应该在不须要这些对象时销毁对他们的引用。測试资源泄露的样例:
class Big {
double[] data = new double[100000];
}; void testLeak() throws InterruptedException{
BoundedBuffer<Big> bb = new BoundedBuffer<Big>(CAPACITY);
int heapSize1 = /* 生成堆的快照 */;
for (int i = 0; i < CAPACITY; i++){
bb.put(new Big());
}
for (int i = 0; i < CAPACITY; i++){
bb.take();
}
int heapSize2 = /* 生成堆的快照 */;
assertTrue(Math.abs(heapSize1 - heapSize2) < THRESHOLD);
}


5 使用回调

6 产生很多其它的交替操作

二、性能測试

性能測试的目标 - 依据经验值来调整各种不同的限值。比如:线程数量、缓存容量等。


1 在PutTakeTest中添加计时功能
基于栅栏的定时器
       this .timer = new BarrierTimer();
this .barrier = new CyclicBarrier(nPairs * 2 + 1, timer); public class BarrierTimer implements Runnable{
private boolean started ;
private long startTime ;
private long endTime ; @Override
public synchronized void run() {
long t = System.nanoTime();
if (!started ){
started = true ;
startTime = t;
} else {
endTime = t;
}
} public synchronized void clear(){
started = false ;
} public synchronized long getTime(){
return endTime - startTime;
}
}


改动后的test方法中使用了基于栅栏的计时器
       void test(){
try {
timer.clear();
for (int i = 0; i < nPairs; i++){
pool .execute( new Producer());
pool .execute( new Consumer());
}
barrier .await();
barrier .await();
long nsPerItem = timer.getTime() / ( nPairs * (long )nTrials );
System. out .println("Throughput: " + nsPerItem + " ns/item");
assertEquals(putSum.get(), takeSum.get() )
} catch (Exception e) {
throw new RuntimeException(e);
}
}


. 生产者消费者模式在不同參数组合下的吞吐率
. 有界缓存在不同线程数量下的伸缩性
. 怎样选择缓存的大小
       public static void main(String[] args) throws InterruptedException {
int tpt = 100000; // 每一个线程中的測试次数
for (int cap = 1; cap <= tpt; cap *= 10){
System. out .println("Capacity: " + cap);
for (int pairs = 1; pairs <= 128; pairs *= 2){
TimedPutTakeTest t = new TimedPutTakeTest(cap, pairs, tpt);
System. out .println("Pairs: " + pairs + "\t");
t.test();
System. out .println("\t" );
Thread. sleep(1000);
t.test();
System. out .println();
Thread. sleep(1000);
}
}
pool .shutdown();
}


查看吞吐量/线程数量的关系


2 多种算法的比較

3 响应性衡量

三、避免性能測试的陷阱

1 垃圾回收

2 动态编译

3 对代码路径的不真实採样

4 不真实的竞争程度

5 无用代码的消除


四、其它的測试方法

1 代码审查

2 静态分析工具 

     FindBugs、Lint
3 面向方面的測试技术

4 分析与监測工具





版权声明:本文博主原创文章,博客,未经同意不得转载。

《Java并发编程实战》第十二章 测试并发程序 读书笔记的更多相关文章

  1. 《Java并发编程实战》第三章 对象的共享 读书笔记

    一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见 ...

  2. 《Java并发编程实战》第七章 取消与关闭 读书笔记

        Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...

  3. 《Java并发编程实战》第四章 对象的组合 读书笔记

    一.设计线程安全的类 在设计线程安全类的过程中,须要包括下面三个基本要素:  . 找出构成对象状态的全部变量.  . 找出约束状态变量的不变性条件.  . 建立对象状态的并发訪问管理策略. 分析对象的 ...

  4. 《Java并发编程实战》第十章 避免活跃性危急 读书笔记

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/love_world_/article/details/27635333 一.死锁 所谓死锁: 是指两 ...

  5. 《Java并发编程实战》第八章 线程池的使用 读书笔记

    一.在任务与运行策略之间的隐性解耦 有些类型的任务须要明白地指定运行策略,包含: . 依赖性任务.依赖关系对运行策略造成约束.须要注意活跃性问题. 要求线程池足够大,确保任务都能放入. . 使用线程封 ...

  6. 并发编程从零开始(十二)-Lock与Condition

    并发编程从零开始(十二)-Lock与Condition 8 Lock与Condition 8.1 互斥锁 8.1.1 锁的可重入性 "可重入锁"是指当一个线程调用 object.l ...

  7. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  8. 《Java并发编程实战》第六章 任务运行 读书笔记

    一. 在线程中运行任务 无限制创建线程的不足 .线程生命周期的开销很高 .资源消耗 .稳定性 二.Executor框架 Executor基于生产者-消费者模式.提交任务的操作相当于生产者.运行任务的线 ...

  9. 《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

    造成开销的操作包含: 1. 线程之间的协调(比如:锁.触发信号以及内存同步等) 2. 添加�的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 执行速度涉及下 ...

随机推荐

  1. spring boot 2.x Path with "WEB-INF" or "META-INF"

    学习spring boot 2.x时,使用jsp作为前端页面.在application.properties配置了jsp所在位置 spring.mvc.view.prefix:/WEB-INF/vie ...

  2. MySQL的安装及使用教程

    MySQL的安装及使用教程 一.  MySQL的下载及安装 首先登陆MySQL的官网,选择Downloads→Windows→MySQL Installer→Windows(x86,32-bit),M ...

  3. Android 系统状态栏一体化实现

    自上周更新了QQ手机client.对于新版本号的QQ,系统状态栏也有蓝色色调,看起来有种清爽感觉.于是想自已也实现这样的效果,随查阅资料,自已调试实现这样的效果.Android 系统4.4以上都能够具 ...

  4. if..... if..... 和if..... else if.....

    曾经一度认为没有区别,,在有的时候是没有区别的,,但是有些时候则不可相互替换 这两个是有区别的 if..... if..... 是不相关的.只要各自判断两部分的条件即可,两个都会执行 if.... e ...

  5. 代码从windows下visual studio到andriod平台迁移的修改记录

    前言 前言也是迁言,从windows的visual studio 2012平台迁移到android平台上,需用修改挺多的代码和需用注意地方. 我们当然的平台当初就考虑了其他平台跨平台的应用问题,所以一 ...

  6. Spring boot(一) 入门

    本系列基于Eclipse 4.7 .JDK 8 一.下载STS (1)STS 注意自己的eclipse版本. 在 Update Site Archives 里面选择对应eclipse的版本下载. (2 ...

  7. ztree实现左边动态生成树,右边为具体信息功能

    页面原型图: 图片.png 功能需求:点击左边树上的子节点,像后台发送请求,将请求到的信息展示在右边的表单里面 前端代码实现: 引入css文档: <link rel="styleshe ...

  8. [HTML] Creating visual skip links in HTML and CSS

    Skip links are an extremely helpful navigation pattern for keyboard and screen reader users, since t ...

  9. iOS开发ARC与MRC下单例的完整写法与通用宏定义

    #import "XMGTool.h" /** * 1:ARC下的完整的单例写法:alloc内部会调用+(instancetype)allocWithZone:(struct _N ...

  10. hash_map原理及C++实现

    一.数据结构:hash_map原理  hash_map基于hash table(哈希表).哈希表最大的长处,就是把数据的存储和查找消耗的时间大大减少,差点儿能够看成是常数时间:而代价不过消耗比較多的内 ...