临时
This work was partially performed when the first author was
a visitor at NCSU, supported by a fellowship from the University
of Pisa and MURST, Italy.
This work was supported in part by NSF grant CCR-9320992
Design of a Toolset for Dynamic Analysis
of Concurrent Java Programs
Alessio Bechini
Dipartimento di Ingegneria dell’Informazione
Facoltà di Ingegneria - Università di Pisa
via Diotisalvi, 2 56100 Pisa, Italy
alessio@pical3.iet.unipi.it
Kuo-Chung Tai
Department of Computer Science
North Carolina State University
Raleigh, North Carolina 27695-7534, USA
kct@csc.ncsu.edu
Abstract
The Java language supports the use of monitors, sockets,
and remote method invocation for concurrent programming.
Also, Java classes can be defined to simulate other
types of concurrent constructs. However, concurrent Java
programs, like other concurrent programs, are difficult to
specify, design, code, test and debug. In this paper, we
describe the design of a toolset, called JaDA (Java
Dynamic Analyzer), that provides testing and debugging
tools for concurrent Java programs. To collect run-time
information or control program execution, JaDA requires
transformation of a concurrent Java program into a
slightly different Java program. We show that by modifying
Java classes that support concurrent programming,
Java application programs only need minor modifications.
We also present a novel approach to managing
threads that are needed for testing and debugging of
concurrent Java programs.
1. Introduction
The Java language supports concurrent programming
in several forms. Java allows the use of monitors to
express concurrency involving shared variables. For
distributed programming, Java provides classes and
packages for supporting the use of sockets and remote
method invocation. Due to the nature of object-oriented
programming, Java classes and packages can be defined
to simulate other types of concurrent programming
constructs. For example, a package defined in [7] contains
classes for simulating the semaphore construct and
various types of message passing constructs.
Concurrent programs are difficult to specify, design,
code, test and debug. One reason is the existence of race
conditions due to the unpredictable rates of progress of
concurrent processes. As a consequence, multiple executions
of a concurrent program with the same input may
exercise different sequences of interactions through
shared variables or messages and may produce different
results. This nondeterministic execution behavior makes it
difficult to understand the behavior of concurrent programs.
There are two general approaches to analyzing the
behavior of a program. Static analysis of a program
determines properties of the program without executing
the program. Dynamic analysis of a program involves
executing the program and analyzing the collected runtime
information.
In this paper, we describe the design of a toolset,
called JaDA, for dynamic analysis of concurrent Java
programs. In section 2, we give a review of dynamic
analysis of concurrent programs. In section 3, we describe
the goals of JaDA. In section 4, we present the architecture
of JaDA and explain major design decisions. In
section 5, we report the current status of JaDA’s implementation.
Finally, we conclude this paper in section 6.
2. Review of dynamic analysis of concurrent
programs
Dynamic analysis of concurrent programs has two
major types of analysis: testing and debugging, which are
discussed in §2.1 and §2.2 respectively. Approaches to
building dynamic analysis tools for concurrent programs
are described in §2.3.
An execution of a concurrent program exercises one
or more synchronization events (or SYN-events). An
actual or expected execution of a concurrent program can
be characterized by its sequence of synchronization
events, referred to as a synchronization sequence (or SYNsequence).
The definitions of SYN-events and SYNsequences
of a concurrent program are based on the
concurrent programming constructs used in the program.
How to define SYN-events and SYN-sequences for
various concurrent-programming constructs has been
shown in [3, 12, 13]. In the remainder of this paper,
general issues on dynamic analysis of concurrent programs
are discussed in terms of SYN-events and SYNsequences.
By doing so, this discussion can be applied to
different concurrent programming constructs.
Let P be a concurrent program. A SYN-sequence is
said to be feasible for P with input X if this SYNsequence
can possibly be exercised during an execution
of P with input X. A SYN-sequence is said to be valid for
P with input X if, according to the specification of P, this
SYN-sequence is expected to be the SYN-sequence of an
execution of P with input X.
2.1 Testing of concurrent programs
Below we briefly describe two basic approaches to
testing concurrent programs. More details about these and
other approaches can be found in [13].
Nondeterministic testing of a concurrent program P
involves the following steps:
1) Select a set of inputs of P,
2) For each selected input X, execute P with X many
times and examine the result of each execution.
The purpose of multiple executions of P with input X is to
exercise different feasible SYN-sequences and increase
the chance of detecting faults. One technique for increasing
the likelihood of exercising different SYN-sequences
is to insert sleep (or delay) statements into P with the
length of sleep randomly chosen.
Deterministic testing of P involves the following steps:
1) Select a set of tests, each of the form (X, S), where X
and S are an input and a SYN-sequence of P respectively.
2) For each selected test (X, S),
– force a deterministic execution of P with input X
according to S. This forced execution determines
whether S is feasible for P with input X.
– compare the actual and expected results (including
the output, feasibility of S, and termination condition)
of the forced execution. If the actual and expected
results are different, a fault is detected.
Deterministic testing allows carefully selected SYNsequences
to be used to test specific portions or paths of
P. Also, deterministic testing can detect the existence of
invalid, feasible SYN-sequences of P and valid, infeasible
SYN-sequences of P (Nondeterministic testing can only
detect the existence of invalid, feasible SYN-sequences of
P).
2.2 Debugging of concurrent programs
Many debugging techniques for concurrent programs
have been developed [9, 15]. Below we briefly describe
debugging techniques that are related to JaDA:
· Collection of SYN-sequences: The collected SYNsequences
can be used for replay of previous executions
(see below), and they can also be modified to generate
new SYN-sequences for deterministic testing.
· Replay of SYN-sequences: The replay of a previous
execution of a concurrent program can be accomplished
by deterministic testing of this program with the input and
SYN-sequence of the execution. Thus, replay is a special
case of deterministic testing with the use of feasible SYNsequences.
· Vector timestamps: To identify the causal relationship
between events in a collected SYN-sequence, vector
timestamps for these events are needed. The computation
of vector timestamps for message-passing programs is
discussed in [5, 11]. The use of vector timestamps for
concurrent programs using shared variables is described
in [10]. How to compute vector timestamps for programs
using both messages and shared variables, according to
strong and weak happened-before relations, is shown in
[1].
· Race analysis: Race analysis of a collected SYNsequence
is to identify race conditions in the SYNsequence.
The collected SYN-sequence of an execution of
a message-passing program is a sequence of send and
receive events. For a receive event r in a trace T, its race
set is the set of messages in T that have a race with the
message received at r and can be received at r during
some possible executions of the same program with the
same input. How to perform race analysis of SYNsequences
of message-passing programs is shown in [14].
How to perform race analysis for programs using shared
variables is shown in [1].
Note that the computation of vector timestamps and
race analysis for a collected SYN-sequence can be performed
either on-the-fly (i.e., during the collection of the
SYN-sequence) or post-mortem (i.e., after the collection
of the SYN-sequence).
2.3 Approaches to building dynamic analysis
tools for concurrent programs
To implement testing and debugging techniques for
concurrent programs requires the construction of tools.
Two basic approaches to building such tools for programs
written in a concurrent language L are discussed below.
An implementation-based approach is to modify one
or more of the three components in the implementation of
L: the compiler, the run-time system, and the operating
system. These modifications enable the collection and
forced execution of SYN-sequences during an execution
of a program written in L. For example, many implementation-
based debuggers allow the programmer to directly
control execution by performing “scheduler-control”
operations such as setting breakpoints, selecting the next
running process, rearranging processes in various queues,
and so on [9]. A recent example of this approach, in
which the layered structure of the run-time system is
used, is presented in [2].
A language-based approach for L has two steps. The
first step is to define the format of SYN-sequences for L
in terms of the synchronization constructs available in L.
The second step is to develop program transformation
tools for L in order to support SYN-sequence collection
and execution. Language-based tools for supporting
nondeterministic and deterministic testing of concurrent
Ada programs and concurrent programs using the semaphore
and monitor constructs have been implemented
[3, 12].
3. Goals of JaDA (Java Dynamic Analyzer)
JaDA (Java Dynamic Analyzer) is a toolset for dynamic
analysis of concurrent Java programs. The goals of
JaDA are the following:
a) to investigate the use of object-oriented technology
for building dynamic analysis tools for concurrent
Java programs,
b) to provide an integrated and extensible environment
that allows easy implementation of different dynamic
analysis techniques for concurrent Java programs,
and
c) to support empirical studies of dynamic analysis
techniques for concurrent Java programs.
In the remainder of this section, we discuss two issues of
JaDA from the user’s viewpoint. The next two sections
describe the architecture and implementation of JaDA.
Issue 1: Scope of concurrent Java programs
As mentioned earlier, Java supports the use of monitors,
sockets, and remote method invocation, and additional
Java classes and packages can be defined to simulate
other types of concurrent constructs. Thus, one design
issue is the scope of concurrent Java programs covered by
JaDA. Our decision is to let JaDA cover built-in and
simulated concurrent constructs in Java. In the following
discussion, packages that simulate concurrent constructs
are referred to as “communication packages”.
Issues 2: Transformation of concurrent Java programs
Since JaDA does not access the implementation of
Java, the only choice is to apply the transformation-based
(or language-based) approach mentioned in §2.3. JaDA
applies the following two types of transformation for
dynamic analysis:
a) If the user’s program uses built-in concurrent constructs
(e.g., synchronized statements and monitors),
it is transformed in order to support dynamic analysis.
This type of transformation is illustrated in
fig. 1B.
b) If the user’s program uses simulated concurrent
Fig. 1 - Representation of the structure of a Java program utilizing both built-in and communication packages.
Diagrams B) and C) illustrate two types of transformation for dynamic analysis. The solution shown in C) allows a
reduction of changes in the user’s program and makes most of such changes transparent to the programmer.
User’s Program
Interaction between modules
Transformation of the user’s program
and communication packages
Transformed part of a module
Legend
Original System
Transformation
of the user’s program only
Packages
Communication
B)
Built-in Packages and APIs
A) C)
constructs provided by communication packages, it is
transformed and combined with transformed communication
packages in order to support dynamic
analysis. This type of transformation is illustrated in
fig. 1C.
(If the user’s program uses both built-in and simulated
concurrent constructs, both types a) and b) of transformation
need to be applied. For the sake of simplicity, we do
not consider this case in the following discussion.)
For a Java program using simulated concurrent constructs,
type a) transformation could be applied. However,
type a) transformation has the following disadvantages:
· Type a) usually requires many changes in the user’s
program and thus make the program difficult to understand.
· Automatic transformation for type a) may be difficult
to do for some simulated concurrent constructs.
On the contrary, type b) transformation has the following
advantages:
· Type b) requires very few changes in the user’s
program, due to the changes made in communication
packages.
· The original and transformed packages can be used in
different phases of software development.
So, for a Java program using simulated concurrent constructs,
the transformation of type b) is more suitable than
the other, and it allows to locate most of the changes in
the communication package.
In fig. 1, it is worth noticing how the interfaces between
different modules are involved in the transformations
of type a) and b).
4. Architecture of JaDA
In this section we show the principal aspects of the
structure of JaDA, focusing on the problems we have
found and the solutions we have adopted.
JaDA is intended to provide the following capabilities,
which are discussed in §2.2: (Additional capabilities
may be considered later.)
a) collection of traces (or SYN-sequences)
b) replay of traces
c) computation of vector
d) race analysis
In JaDA, capabilities a), b) and c) are performed during
program execution, based on the use of program transformation,
as discussed in §3. The user’s program and
communication packages are transformed only once. The
transformed program and packages allow the user to
choose one of three execution modes: normal execution,
execution for collecting traces, and execution for replay.
In addition, the transformed program and packages allow
the user to specify whether computation of vector timestamps
is performed during an execution. Capability d) is
performed on collected traces in post-mortem fashion.
One major design issue of JaDA is the use of centralized
or distributed control for performing capabilities
a), b) and c). Our decision is to use distributed control, in
conjunction with partially ordered traces, in order to get
better performance. Distributed control allows concurrent
accesses to files for keeping a partially ordered trace.
During replay of a partially ordered trace, distributed
control allows concurrent events to be repeated in an
arbitrary order and thus avoids unnecessary delay. Furthermore,
distributed control supports the use of JaDA on
multiple JVMs. Fig. 3 shows how the distribution of
control is used in trace file management. Of course, in
most of the cases a concurrent Java program is executed
on a monoprocessor machine, so the benefit is negligible.
However, if the program is spread out on JVMs on
different physical machines, the improvement coming
from a distributed control is significant.
4.1 Management of threads
In Java each thread is created through an instance of
class Thread, which has several static methods for
managing threads. For dynamic analysis, we need to keep
additional information for each thread. How to handle
such information is a critical issue. Our solution is define
a new class called JKThread such that each thread
becomes an instance of class JKThread. In order to
make all the transformations as transparent to the programmer
as possible, we have decided to “sandwich”
JKThread between the Thread class (belonging to the
java.lang package) and the class with the thread code.
Fig. 2A and fig. 2B show the insertion we are talking
about. This is a good solution because we can create each
thread directly as an instance of JKThread (instead of
Thread). All the portions of the transformed program
can access the additional data corresponding to the thread
that is executing them in the following way (fig. 2C): the
static currentThread() method in Thread returns a
reference to a Thread object, but the new class
JKThread extends it and so it is possible to apply a cast
operator to get a reference to the current JKThread
object (i.e., we can use “down-casting”).
Class JKThread is implemented as follows. Constructors
in class JKThread are expanded versions of
corresponding constructors in class Thread. For example,
below is JKThread’s constructor that has a Runnable
object as a parameter:
public JKThread (Runnable target,
int numThreads, int whatIAm) {
super(target);
vectClock = new VectTS(numThreads);
[ ... ]
this.whatIAm = whatIAm;
}
In the above constructor, the first statement calls the
corresponding constructor in class Thread and other
statements assign values to variables for keeping additional
information for a thread.
The direct use of class JKThread, however, is impossible
for some threads. Below are two examples:
· The “main” thread of a Java program is created by
the system and thus is not an instance of class
JKThread. One solution to this problem is to disallow
the main thread to perform any synchronization
events.
· In a Java GUI environment, threads are created by
the system to perform operations associated with external
events such as clicking on a button. Such
threads are not instances of class JKThread.
Considering these two facts, it is not always possible to
use naively the JKThread class, but sometimes additional
mechanisms are needed in order to use properly
such class in different contexts.
4.2 Definition of event formats
Every time we want to gather information about the
events exercised during an execution, we must specify the
types of the events and we must organize the event data in
an appropriate format [13]. In our case, we can distinguish
the following general types of event: synchronous
send, synchronous receive, asynchronous send, asynchronous
receive, read on a shared object, and write on a
shared object. Considering the specific packages used by
the program, many variations could be done within these
basic categories. Since different types of events require
different sets of data, it is impossible define a unique
event format for all types of events. Below are some
commonly used data associated with an event:
· the thread executing the event
· vector timestamp for the event
· type of the event
· name of the event
· the SYN-object accessed by the event (e.g., the
variable accessed by a read or write, or the channel
accessed by a send or receive)
One major design issue is whether the size of a vector
timestamp is fixed (i.e., whether the number of threads in
a Java program is fixed). JaDA allows the number of
threads in a Java program to change during an execution.
This decision makes the implementation of vector timestamps
complicated, but allows more Java programs to
use JaDA.
We have defined a class called JKSynEvent, which
contains information common to all types of events. For
each type of event, we define a new class, which inherits
from JKSynEvent and contains additional data needed
for the event type. By doing so, we keep a hierarchical
structure of event formats and allows the flexibility of
defining new event formats if necessary.
4.3 Logging traces to file
Dynamic analysis implies to deal with information
about the events performed during an execution. This
information must be structured in such a way to allow an
easy and quick access. As mentioned earlier, JaDA uses
Fig. 2 - Dynamic analysis requires additional information to be maintained for each thread (especially in the tracing
phase). Such information can be placed in an instance of the class called JKThread, sandwiched between the
classes of a specific thread and the class Thread (belonging to java.lang package). In the transformed program, a
cast operator on the static method currentThread() of class Thread is used to access the additional thread
information.
Class diagram for a typical class implementing
the body of a Java thread
class Thread
class Object
class MyOwnThread
interface Runnable
java.lang
class Thread
class Object interface Runnable
class MyOwnThread
additional info about
thread status and
A)
methods for managing it
any object referred
class JKThread
java.lang
B) Insertion of a class containing information and methods
to deal with additional status variables
for managing vector timestamps
corresponding to the current thread through
C) Getting a reference to the JKThread object
a casting on the currentThread() method
Thread object
public static Thread currentThread();
JKThread object
casting effect
r = (JKThread) currentThread();
...
...
by a MyOwnThread object
distributed control, in conjunction with partially ordered
traces, for dynamic analysis. There are two different types
of partially ordered traces [13]:
a) based on threads
b) based on SYN-objects.
A thread-based partially ordered trace has one “partial”
file (or trace) for each thread, while a SYN-object-based
partially ordered trace has one “partial” file for each
SYN-object. JaDA chooses to use SYN-object-based
partially ordered traces. We define a class called JKSyncObjCtrl,
which contains methods for accessing SYNobject-
based partial files. For each SYN-object, an
instance of JKSyncObjCtrl is created to control
accesses to this SYN-object.
Fig. 3 shows the interactions between threads and
SYN-object-based partial files. Each partial file has the
extension “.pef,” denoting “partial event file.” Also, each
SYN-object-based partially ordered trace contains a
header file to contain information related to the trace.
4.4 On-the-fly computation of vector timestamps
Every time we want to associate information to
events of an execution in order to understand their causal
relationships, we have to compute a vector timestamp for
each event [11]. This vector timestamp assignment
operation can be done in two different ways: using either
“on the fly” or “post mortem” algorithms [1]. Both these
solutions have advantages and drawbacks, but we have
chosen the first one for several reasons:
· We do not consider real-time constraints, so we do
not care about any “probe effect” and any
“heisenbugs” [6]. Clearly, treating timestamp assignment
“on the fly” augments the interference in
the original program, but we have to think that a program,
if correct, must behave in the expected way despite
of any delay coming from the instrumentation.
· Being the trace files usually big, it is convenient to
assign timestamps during the execution, when the file
must be used anyway, instead of manipulating the
files twice (first for event logging, then for timestamp
assignment).
· The on-the-fly assignment avoids long waiting before
starting race analysis.
· The time overhead of timestamp computing and
logging is distributed during the whole execution
time.
4.5 Using templates for dealing with shared data
We have already discussed the reasons for placing
most of the transformations in the used packages instead
Fig. 3 - A schematic view of the interaction among threads and partial files corresponding to SYN-objects. Each
(transformed) thread has a private instance of the class JKThread, just to contain the additional data required by
dynamic analysis. Moreover, the methods to manage the partial files are placed inside the JKSyncObjCtrl class,
and every thread must use them for accessing the partial files.
Thread #N
Instance of class
JKThread
for thread #1
Instance of class
JKThread
for thread #2
Instance of class
JKThread
for thread #N
is accessed through an instance
Info in every partial file
of class JKSyncObjCtrl
Use of the M SYN-objects
by the N transformed threads
the transformed program
Threads of
Instance of class
SObjName1.pef
JKSyncObjCtrl
Instance of class
JKSyncObjCtrl
Instance of class
JKSyncObjCtrl
Every thread uses an instance
of class JKThread
for additional local info
necessary for dynamic analysis
Code of
Thread #1
Code of
Thread #2
Code of
Myname.hef SObjName2.pef SObjNameM.pef
Header file Partial file #1 Partial file #2 Partial file #M
Trace file system (partial ordering based on SYN-objects)
of the program itself. This operation is permitted because
of our attention toward high-level events. Sometimes, in
our analysis we could have to consider accesses to shared
data made through the direct use of basic Java synchronization
capabilities. So, we have to remember that Java
objects have an implicit lock which can be handled
implicitly (through the statement synchronized) for
assuring mutual exclusion in accessing them or specific
parts of code (indications on how to use this and other
Java basic concurrent constructs can be found in [8] ).
In dealing with accesses to shared data in the analyzed
program, we must insert additional code directly
within the original program. This insertion must provide
the tracing and replay capabilities and must encompass
the variables and the procedures for treating vector
timestamps, according with the algorithms described in
[1]. Even in these cases, the transformed program should
keep visible the original control structure. For assuring
such characteristic, we have decided to design some
templates for the additional code to place where shared
accesses are present. In our approach, we assume that a
synchronized class is used for hiding shared data, and
its methods provide read and write operations on them
(i.e., a synchronized class is used as a monitor-like
construct). This way of coping with the problem of shared
data is very similar to that shown in [3].
4.6 Race analysis of collected traces
Once a trace has been collected, we can use it for
verifying properties of the execution. A particular type of
inspection of the characteristics of an execution can be
done through race analysis, as previously mentioned in
§2.2. As Java threads can interact using both message
passing and shared variables, in JaDA it is possible to
compute race sets for both receive and read events. This
operation is not particularly difficult, since the trace
contains, for each event, the relative vector timestamp.
Moreover, there is the possibility of using two different
relations for causality detection: weak and strong happened-
before [1]. Of course, the general algorithms
presented in [14] and [1] had to be adapted to the particular
structure of the Java environment, tailoring them to
the peculiarities of the concurrent constructs and SYNobjects
used in the program.
5. Current implementation of JaDA
At present JaDA implementation covers the basic
features for shared data access, as described in §4.5, and
some of the concurrent constructs in package Synchronization
in [7].
Without describing in depth the techniques used for
the transformation of the package, we can say that the
new code inserted in each class try to leave clearly visible
the original one, and that the added portions try to mimic
the structure and the behavior of the original code. It
could be interesting to underline that the code for tracing
purposes is placed after the lines corresponding to the
event, despite that for replay, which has to be put directly
before the event. Moreover, as a consequence of the
application of the philosophy of hiding the transformations
to the original program, the number and the types of
the parameters in every public method must remain the
same.
JaDA is composed by the following three packages:
· JaDAkernel: it contains classes and interfaces for
supporting basic functions in dynamic analysis and
providing services needed by other JaDA packages.
· JaDAmsg: it contains classes and interfaces that
implement various message-passing constructs with
dynamic analysis capabilities.
· JaDAvar: it contains classes and interfaces that
implement various variable-sharing constructs with
dynamic analysis capabilities.
Currently, we have implemented classes in package
JaDAkernel to provide functions mentioned in §4.1
through §4.4. For package JaDAmsg, we have revised
classes in [7] to add tracing and replay capabilities. For
package JaDAvar, we plan to include classes and interfaces
that simulate various types of semaphores and
monitors.
Considering the goals of JaDA, an academic study
could be limited to a little portion of a single package, but
in this way there is no serious possibility to do relevant
empirical experiments using JaDA: the larger the scope of
the tool, the better the benefits we can have through it.
6. Conclusions
In recent years, several approaches were proposed for
analyzing, testing and debugging concurrent programs.
Since Java is becoming a major language for writing
concurrent programs, static and dynamic analysis of
concurrent Java programs are important research topics.
Some issues on static analysis of concurrent Java programs
are discussed in [4].
In this paper, we have described the design (and
some implementation details) of JaDA, which provides
testing and debugging tools for concurrent Java programs.
To collect run-time information or control program
execution, JaDA requires transformation of a concurrent
Java program into a slightly different Java program. We
have shown that by modifying Java classes that support
concurrent programming, Java application programs only
need minor modifications. We have also presented a
novel approach to managing threads that are needed for
testing and debugging of concurrent Java programs.
As mentioned in §3, JaDA is intended to accomplish
the following goals for concurrent Java programs:
a) to investigate the use of object-oriented technology
for building dynamic analysis tools,
b) to provide an integrated and extensible environment
that allows easy implementation of different dynamic
analysis techniques, and
c) to support empirical studies of dynamic analysis
techniques.
In this paper, we have addressed major issues related to
the first two goals.
We have made significant progress in the implementation
of JaDA. Soon we will use JaDA to carry out some
empirical studies of testing and debugging of concurrent
Java programs. We will also investigate extensions of
JaDA to increase the quality and reliability of concurrent
Java programs.
Acknowledgments
We want to express our grateful acknowledgments to
Robert Harris, Bengi Karacali, Naveen Sarabu, and Jun
Zhou for their contributions to the design and implementation
of JaDA. We also wish to thank the anonymous
reviewers for their helpful comments on an earlier version
of this paper.
References
[1] A. Bechini and K. C. Tai, “Timestamps for Programs
Using Messages and Shared Variables,” in Proc. of 18th
IEEE Inter. Conf. on Distributed Computing Systems, May
1998.
[2] A. Bechini, J. Cutajar, and C. A. Prete, “A Tool for Testing
of Parallel and Distributed Programs in Message Passing
Environments,” in Proc. of 9th Mediterranean Electrotechnical
Conf. , May 1998
[3] R. H. Carver and K. C. Tai, “Replay and Testing for
Concurrent Programs,” IEEE Software, March 1991, pp.66-
74
[4] J. C. Corbett, “Constructing Compact Models of Concurrent
Java Programs,” in Proc. of ACM Inter. Symp. Software,
Testing and Analysis (ACM Software Engineering
Notes, Vol. 23, No. 2, March 1998) pp. 1-11
[5] C. J. Fidge, “Logical Time in Distributed Systems,” IEEE
Computer, Aug. 1991, pp. 28-33.
[6] J. Gait, “A Probe Effect in Concurrent Programs,” Software-
Practice and Experience, Vol.16, No. 3, March 1986,
pp. 225-233
[7] S. J. Hartley, “Concurrent Programming: The Java
Programming Language,” Oxford University Press, 1998.
[8] D. Lea, “Concurrent Programming in Java: Design
Principles and Patterns,” Addison Wesley, 1997
[9] C. E. McDowell and D. P. Helmbold, “Debugging Concurrent
Programs,” ACM Computing Surveys, Vol. 21, No. 4,
Dec. 1989, pp. 593-22
[10] R. H. B. Netzer, “Optimal Tracing and Replay for Debugging
Shared-Memory Parallel Programs,” in Proc.
ACM/ONR Workshop on Parallel and Distributed Debugging,
1993, pp. 1-11
[11] R. Schwartz, and F. Mattern, “Detecting Causal Relationships
in Distributed Computations: in Search of the Holy
Grail,” Distributed Computing, Vol. 7, 1994, pp. 149-174.
[12] K. C. Tai, R. H. Carver, and E. E. Obaid, “Debugging
Concurrent Ada Programs by Deterministic Execution,”
IEEE Trans. Soft. Eng., Vol. 17, No. 1, Jan. 1991, pp. 45-
63
[13] K. C. Tai and R. H. Carver, “Testing of Distributed
Programs”, chapter 33 of Handbook of Parallel and Distributed
Computing, edited by A. Zoyama, McGraw-Hill,
1996, pp. 955-978
[14] K. C. Tai, “Race Analysis of Traces of Asynchronous
Message-Passing Programs,” Proc. 17th IEEE Inter. Conf.
Distributed Computing Systems, 1997, pp. 261-268
[15] J. J. P. Tsai and S. J. H. Yang, eds., “Monitoring and
Debugging of Distributed Real-Time Systems,” IEEE
Computer Society, 1995.