Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

Java 多线程程序的测试

Posted on 05/06/201406/23/2019 by 四火

work

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

首先,需要明确的是,用 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 程序员的一些错误,收集运行时的信息,推断测试向量和创建相应的测试驱动器等等。它从系统上探测程序所有可能的执行路径,以发现死锁或未处理异常之类情形。

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

×Scan to share with WeChat

你可能也喜欢看:

  1. java.util.concurrent 并发包诸类概览
  2. 动手实现随机验证码
  3. 看 JDK 源码,解几个疑问
  4. Java 多线程发展简史
  5. EasyMock、EasyMock Class Extension 和 PowerMock

1 thought on “Java 多线程程序的测试”

  1. 屠龙 says:
    05/24/2014 at 1:58 PM

    说的太好了。

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
  • Anonymous on 我裸辞了
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme