Java多线程程序的测试

标签: Testing Java Thread Weaver 多线程 测试 | 发表时间:2014-05-06 23:47 | 作者:四火
出处:http://www.raychase.net

Java多线程程序的测试

这个问题最初来自于一封公司内部的话题探讨邮件,再加上了一些我的理解。

首先,需要明确的是,用Java通常构建多线程安全的程序“非常”困难,如果还没有体会到“非常”的话,阅读《Java Concurrency in Practice》(中文名叫做《Java并发编程实战》,在我的 书单里面,我认为它基本是最好的系统介绍Java并发的书了)可能可以改变你的看法。

多线程的基础

基础是王道。对于任何一门语言都是如此,有的基础部分是和语言无关的,也有一部分是和Java语言相关的。这里我不过多展开,但是我想提一提对于 JSR规范的理解。通常我们认为Java是一门啰嗦、冗长,容易使用,而且不容易造成破坏的语言,但是,要写完全正确的Java多线程程序,却根本不是这样,需要知道的东西非常多,譬如JSR-133和JSR-166这两个最重要的规范。

良好的设计

这一定是放在第一条。软件质量不是靠测出来的,而是靠设计出来的。尽可能简化问题,主动去避免一些潜在的问题,例如,我在 这篇文章里面谈到了为什么多线程的GUI框架很难被设计出来;再比如 OSCache中受到批评的NeedsRefreshException,设计机制决定了它bug丛生,容易发生死锁。一些被广泛认可的经验总结都是指导软件设计的参考依据。天然地,从设计阶段就让程序对逻辑多线程执行的解读保持清晰和简单,这样的代码才谈得上可靠性。

代码评审

对于一些很难构造测试用例来覆盖的潜在问题, 代码评审几乎成为了最后一道可能系统地发现问题的堤坝。当然,代码评审的局限性很明显,评审的人也很容易陷入细枝末节当中,通常意义上编码习惯和规范性问题,或者是单个类的使用造成的多线程潜在问题容易被发现,但是类和组件之间的并发情况下资源使用和条件关联的问题却很难被发现出来。

辅助工具检查

比如 FindBugs这样的工具,可以看一看 bug description的列表中“ Multithreaded correctness”的部分。当然,类似地,还有 PMD这样的工具。这两个工具都可以针对编译后的class来查错,而不是只是源代码文件。还有一个名为 Coverity Code Advisor的工具,我也是第一次听到,不过所属公司Coverity,王垠曾经写文章评论过,感兴趣的可以去他的博客找。

说了那么多,现在才轮到真正说测试的部分。但是,即便如此,我依然要说,多线程程序的很多问题很难通过测试发现。倚仗测试去保证质量在这里只能说是下下策。

压力测试

首先,需要明确的是,和所有的情况的测试一样,测试出问题只能作为充分条件,不能作为必要条件,即多线程程序测试发现问题只能说明这段多线程程序是有问题的,但是程序有问题却不一定能够通过测试发现。最容易想到的方法大概是通过多线程场景下对多线程的代码逻辑反复执行,特别是做到可控制的压力测试,以期望其中的若干次运行得到非预期的结果。这是最粗暴也是最简单的办法。这样的方法是不可替代的。但是,很多多线程问题的出现情况复杂而且这种方式明显缺乏目的性,有乱枪打鸟的感觉。

Debug大法

很好笑对不对?不要小看了单步执行,尤其对于不同线程中,执行分支路径之间的组合,用单步调试的方法可以模拟出很多种情况,当然,这样的测试必然是白盒的。对于多线程程序的问题,我不相信一个不看被测试代码的黑盒测试人员可以做好这件事情。

多线程程序测试的框架

这个要看具体情况了。在Amazon就有这样的一套内部使用的并发测试框架,在我以前的公司,也见过别的部门有人写过,基本上和传统测试代码编写无异,通过注解或者几个简单的工具类控制,就可以灵活地指定并行测试的参数了,比如线程数量等等(有代码指定,也有命令行参数指定)。

一些有趣的开源库

比如 Thread Weaver,这个库是专门用来写多线程单元测试的,它的原理是在代码中创建一些断点,接着代码执行的时候就可以挂在断点上面,这样就可以测试各种资源争用和条件组合了。给一个简单的例子,首先定义MyList这样的对象:

public class MyWriter {
	private StringBuilder sb = new StringBuilder();
	private int counter = 0;

	public synchronized void write(String s) {
		sb.append(++counter + "-" + s + " ");
	}
	
	public synchronized String toString(){
		return sb.toString();
	}
}

现在测试它:

public class MyWriterTest {
	private MyWriter writer;

	@Test
	public void testThreading() {
		AnnotatedTestRunner runner = new AnnotatedTestRunner();
		// Run all Weaver tests in this class, using MyList as the Class Under Test.
		runner.runTests(this.getClass(), MyWriter.class);
	}

	@ThreadedBefore
	public void before() {
		writer = new MyWriter();
	}

	@ThreadedMain
	public void mainThread() {
		writer.write("MainA");
		writer.write("MainB");
	}

	@ThreadedSecondary
	public void secondThread() {
		writer.write("SecondA");
		writer.write("SecondB");
	}

	@ThreadedAfter
	public void after() {
		System.out.print(writer.toString());
	}
}

其中@ThreadedBefore和@ThreadedAfter是一对,以前者为例,对于API文档里面的解释:“An annotation that designates part of a test suite that uses the AnnotatedTestRunner framework to perform multithreaded tests. A method tagged with the ThreadedBefore attribute will be run before every test case.”,我最初认为以Threaded开头的注解都会默认启动两个线程并行执行,后来发现确实是执行了两遍,但是通过在@ThreadedBefore/After的代码里面打印线程号发现,这两遍的线程号都是同样的,这里的机制我还不是很清楚;

而对于上述每执行一遍的过程中,又因为有了@ThreadedMain和@ThreadedSecondary这一对,这两个注解修饰的方法会分别放到单独的线程里面去并行执行。

以下是其中一种可能的输出结果:

1-MainA 2-SecondA 3-MainB 4-SecondB 1-MainA 2-SecondA 3-MainB 4-SecondB 

当然,也可能是:

1-MainA 2-SecondA 3-SecondB 4-MainB 1-MainA 2-SecondA 3-MainB 4-SecondB 

等等多种。

Thread Weaver还允许使用CodePosition做精细控制,有一点复杂, 文档里面有例子说明,就像是把debug过程给代码化了。

再比如 JPF,JPF的全称叫做Java Pathfinder,是可以自定义的Java字节码执行环境,经常被用来Java程序调试和校验。有了它,可以发现Java程序员的一些错误,收集运行时的信息,推断 测试向量和创建相应的测试驱动器等等。它从系统上探测程序所有可能的执行路径,以发现死锁或未处理异常之类情形。

 

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

分享到:
你可能也喜欢:

相关 [java 多线程 程序] 推荐:

Java多线程程序的测试

- - 四火的唠叨
这个问题最初来自于一封公司内部的话题探讨邮件,再加上了一些我的理解. 首先,需要明确的是,用Java通常构建多线程安全的程序“非常”困难,如果还没有体会到“非常”的话,阅读《Java Concurrency in Practice》(中文名叫做《Java并发编程实战》,在我的 书单里面,我认为它基本是最好的系统介绍Java并发的书了)可能可以改变你的看法.

Java面试题:多线程,作为Java程序员你不得不懂

- sun - IT程序员面试网
线程:是指进程中的一个执行流程. 线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源. 创建线程有两种方式,如下: 1、 扩展java.lang.Thread类 2、 实现Runnable接口 Thread类代表线程类,它的两个最主要的方法是: run()——包含线程运行时所执行的代码 Start()——用于启动线程.

Java Thread多线程

- - CSDN博客推荐文章
Java Thread多线程. Java 多线程例子1 小例子. super("zhuyong");//设置线程的名字,默认为“TestThread”. Java 多线程例子2 前台线程(用户线程) 后台线程(守护线程 ). 1,setDaemon(true)后就是后台线程(守护线程 ),反之就是前台线程(用户线程).

Java多线程之synchronized

- - CSDN博客推荐文章
这里通过三个测试类阐述了synchronized应用的不同场景. 首先是最基本的synchronized Method的使用.  * @see 概述:Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor) .  * @see 说明:当synchronized关键字修饰一个方法时,则该方法为同步方法 .

java多线程总结

- - Java - 编程语言 - ITeye博客
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 对于直接继承Thread的类来说,代码大致框架是:. class 类名 extends Thread{. * @author Rollen-Holt 继承Thread类,直接调用run方法.             System.out.println(name + "运行     " + i);.

Java多线程学习

- - CSDN博客编程语言推荐文章
  线程是一种轻量级的进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单. 即多个线程可以同时执行,就像有多条流水线一样,可以同时进行工作,是并发执行的.   程序是由进程组成的,进程是由线程组成的.

Java多线程(二)同步

- - CSDN博客编程语言推荐文章
如果你的java基础较弱,或者不大了解java多线程请先看这篇文章 java多线程(一)线程定义、状态和属性. 同步一直是java多线程的难点,在我们做android开发时也很少应用,但这并不是我们不熟悉同步的理由. 希望这篇文章能使更多的人能够了解并且应用java的同步. 在多线程的应用中,两个或者两个以上的线程需要共享对同一个数据的存取.

[Java] Java 多线程案例分析

- - V2EX
现要从 hbase中导出 2016 年整年的,大约 10w只股票行情数据,数据总量约 100t. 汇总到 hdfs中供需求方使用. 已知数据量规模大概是 100t,那么单台机器处理肯定不是不行的,先不说大多数磁盘都没这么大,即便磁盘有这么大,单台机器处理对于内存和 cpu 要求也很高,所以我们将问题一般化,使用数量有限的低配机器.

Java 多线程内存模型

- - ITeye博客
Java 多线程内存模型.       Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果. 在此之前,主流程序怨言(如C/C++等)直接使用物理硬件(或者说操作系统的内存模型),因此,会由于不同的平台上内存模型差异,导致程序在一套平台上并发完成正常,而在另一套平台上并发访问却经常出错,因此经常需要针对不同的平台来编写程序.

Java多线程之wait()和notify()

- - CSDN博客推荐文章
直接看测试代码吧,细节之处,详见注释.  * Java多线程之wait()和notify()的妙用 .  * @see 问题:同时启动两个线程和同时启动四个线程,控制台打印结果是不同的 .  * @see      同时启动两个线程时,控制台会很规律的输出1010101010101010 .  * @see      同时启动四个线程时,控制台起初会规律的输出10101010,一旦某一刻输出一个负数,那么后面的输出就会"一错再错" .