自动检测并行Java程序中的错误

当 CPU 进入多核时代之后,并行编程将更加流行,但是编写并行程序更容易 出错。在开发过程中,工程师能注意到同一个程序在单线程运行时是正确的,但 是在多线程时,它会有可能出错。和并行相关的错误的产生原因通常都非常隐晦 ,而且在一次测试中,它们的出现与否具有很强的随机性。由于程序中多个线程 之间可能以任意的方式交错执行,即使一个并行程序正确的运行了成百上千次, 下一次运行仍然可能出现新的错误。

Multi-Thread Run-time Analysis Tool 是由 IBM 为多线程 Java 程序开发 的运行时分析工具,它可用于分析并查找 Java 代码中的一些不容易发现的潜在 并行程序错误,比如数据竞争 (Data Race) 和死锁 (Deadlock),从而提高并行 程序的代码质量。本文将介绍检测 Java 程序中随机并行错误的一种新工具 (http://alphaworks.ibm.com/tech/mtrat),检查 Java 代码中的潜在的并行程 序错误,从而提高代码的安全性和稳定性,并演示其对于潜在而并未发生的错误 的发掘能力。

概述

Java 编程语言为编写多线程应用程序提供强大的语言支持。但是,编写有用 的、没有错误的多线程程序仍然比较困难。编程语言中线程面临很多挑战。在这 些挑战中,最主要的就是编程复杂度的提高。这些编程复杂度是由同步共享变量 的访问,潜在的依赖于时序的错误和调试和优化并行程序的复杂性造成的。

MTRAT 只所以把不同的技术集成到了一个单一的开发工具中,是为了掩盖工 具内部的复杂性,并使得 MTRAT 方便使用。 MTRAT 主要由以下部分组成,

简单的命令行界面和 Eclipse 插件。输出 MTRAT 检查到的并行错误。

动态的 Java 字节码修改引擎。可以在 Java 类文件被 Java 虚拟机加载的 时候,修改 Java 类。

程序运行时信息收集器。收集程序的动态信息,比如内存访问,线程同步, 创建和结束。

高效的运行时分析引擎。收集到的运行时信息会被在线分析,如果发现潜在 的并行错误,将会通过界面报告给用户。

检测数据竞争

在并行程序中,当两个并行的线程,在没有任何约束的情况下,访问一个共 享变量或者共享对象的一个域,而且至少要有一个操作是写操作,就会发生数据 竞争错误。MTRAT 最强大的功能就是发现并行程序中潜在的数据竞争错误。在下 边的 Java 程序就隐藏了一个潜在的数据竞争错误。

package sample;class Value{private int x;public Value(){x = 0;}public synchronized void add (Value v){x = x + v.get();}public int get() {return x;}}class Task extends Thread{Value v1, v2;public Task (Value v1, Value v2){this.v1 = v1;this.v2 = v2;}public void run() {v1.add(v2);}}public class DataRace{public static void main (String[] args) throws InterruptedException{Value v1 = new Value ();Value v2 = new Value ();Thread t1 = new Task(v1, v2);Thread t2 = new Task (v2, v1);t1.start();t2.start();t1.join();t2.join();}}

类Value声明一个整形域x,一个同步方法add修改这个域,和一个方法get返 回域x的值。类Task以两个类Value的实例来构造。

以 MTRAT 运行类sample.DataRace,可以在运行时刻检查程序中的潜在的数 据竞争错误

$ mtrat -cp . sample.DataRaceData Race 1 : 3 : sample/Value : xThread Thread-3 id: 7 : WRITEsample.Value : get : 15sample.Value : add : 15sample.Task : run : 32Thread Thread-4 id: 8 : READsample.Value : get : 18sample.Value : add : 15sample.Task : run : 32Data Race 2 : 4 : sample/Value : xThread Thread-3 id: 7 : READsample.Value : get : 15sample.Value : add : 15sample.Task : run : 32Thread Thread-4 id: 8 : WRITEsample.Value : get : 15sample.Value : add : 15sample.Task : run : 32

在图形界面Eclipse中运行,得到以下结果:

Figure 1.

MTRAT 报告出了两个数据竞争错误,因为类Task的两个实例会访问类Value的 对象,然而这个共享的对象却没有被一个共同的锁保护。

例如,在并行程序执行过程中,可能存在这样的时刻,一个线程执行方法get 读域x的值,而另外一个线程执行执行方法add写域x。

根据检查结果,MTRAT 发现了两个数据竞争错误,在类sample/Value域x。程 序员在得到这两个数据竞争错误后,很容易就能发现程序中存在两个线程并发访 问同一个对象域的可能。如果两个线程可以顺序访问这个对象域,这两个数据竞 争问题就可以被消除了。

检测死锁

死锁问题也是并行 Java 程序中常见的问题。在 Java 程序中出现死锁,是 因为 synchronized 关键字会造成运行的线程等待关联到某个一个对象上的锁。 由于线程可能已经获得了别的锁,两个线程就有可能等待对方释放掉锁。在这种 情况下,两个线程将永远等待下去。

在下边的 Java 程序就隐藏了一个潜在死锁问题,

class T3 extends Thread{StringBuffer L1;StringBuffer L2;public T3(StringBuffer L1, StringBuffer L2){this.L1 = L1;this.L2 = L2;}public void run(){synchronized (L1){synchronized (L2){}}}}public class Deadlock{void harness2() throws InterruptedException{StringBuffer L1 = new StringBuffer("L1");StringBuffer L2 = new StringBuffer("L2");Thread t1 = new T3(L1, L2);Thread t2 = new T3(L2, L1);t1.start();t2.start();t1.join();t2.join();}public static void main(String[] args) throws InterruptedException{Deadlock dlt = new Deadlock();dlt.harness2();}}

在类 Deadlock 的 harness2 方法中,类 Deadlock 的两个实例被创建,作 为参数传递到类 T3 的构造函数中。在类 T3 的 run 方法中,线程会依次获得 这个两个对象的锁,然后以相反的顺序释放这两个锁。由于两个 StringBuffer 实例以不同的顺序传递给类 T3,两个线程会以不同的顺序获得这两个锁。这样 ,死锁就出现了。

以 MTRAT 运行类 sample.Deadlock,可以在运行时刻检查程序中的潜在的死 锁错误:

$ mtrat -Dcom.ibm.mtrat.deadlock=true -cp . sample.DeadlockThread 7 : Acquire L1 L2Dead Lock 1Thread 7, acquired lock1 -> try lock2 sample/T3 line 109Thread 8, acquired lock2 -> try lock1 sample/T3 line 109Thread 8 : Acquire L2 L1

在图形界面Eclipse中运行,得到以下结果:

图 2.

在 MTRAT 的死锁检查报告中我们可以发现,线程 Thread 7 已经获得了锁 lock1,在程序 109 行试图获得锁 lock2。然而,线程 Thread 8 已经获得了锁 lock2,在程序 109 行试图获得锁lock1。

根据 MTRAT 的死锁检查报告,程序员可以很容易得知道,这个死锁问题是由 于两个线程按照相反的顺序上锁造成的。避免这种问题的一种方法是让代码按固 定的全局顺序获取锁。那么如果两个线程按照一致的顺序去上锁,死锁错误就可 以被消除了。

void harness2() throws InterruptedException{StringBuffer L1 = new StringBuffer("L1");StringBuffer L2 = new StringBuffer("L2");Thread t1 = new T3(L1, L2);Thread t2 = new T3(L1, L2);t1.start();t2.start();t1.join();t2.join();}

结束语

在本文中,我们展示了如何检查并行 Java 程序中潜在的错误,比如数据竞 争和死锁。通过使用 MTRAT,您可以在程序开发阶段发现用肉眼难以发现的并行 程序错误。该工具使开发正确和高质量的并行程序变得更加容易。

眼睛可以近视,目光不能短浅。

自动检测并行Java程序中的错误

相关文章:

你感兴趣的文章:

标签云: