To avoid race conditions, programmers often adopt a programming discipline in which shared resources are guarded by locks. Before accessing any shared structure, the necessary lock must be acquired. This discipline ensures that no two threads ever access the same resource at the same time. Using locks in this fashion shifts the problem of preventing races to one of enforcing the locking discipline.
This project summary describes a static analysis technique that supports this locking discipline in concurrent Java programs. The analysis technique, which is presented in the form of the static type system, was designed so that it has the following desirable features:
class Counter { private int c = 0 /*# guarded_by this */; private void set(int x) /*# requires this */ { c = x; } public void increment() { synchronized(this) { set(c+1); } } }The guarded_by annotation on the field c indicates which lock must be held to access that field, and the requires clause on the set method indicates which lock (or locks) must be held prior to invoking that method. To typecheck a program, a conservative approximation of the set of locks held at each program point is determined, and the checker then verifies whether the constraints expressed in the annotations are satisfied on each field access and method invocation.
As part of this verification process, the analysis needs to determine whether a specific lock is in the lock set. The set membership test requires some notion of equality between lock names, but since our analysis cannot rely on run-time values, we approximate run-time value equivalence with syntactic equality.
Another common programming idiom is to create unsynchronized classes and require the client to provide the necessary synchronization. This type of class may be expressed in our type system using classes parameterized by lock names. The following code example shows how to write a counter monitored by a lock in the client code:
class Counter/*# {ghost Object o} */ { private int value = 0 /*# guarded_by o */; private void set(int x) /*# requires o */ { value = x; } public void increment() /*# requires o */ { set(value+1); } } Object mutex = new Object(); Counter/*#{mutex}*/ c = new Counter/*#{mutex}*/();It is often the case that a significant fraction of a concurrent program does not use synchronization at all. To avoid the need to require locks on objects that are not shared between threads, we introduce the notion of a thread_local class into the type system. A thread_local class is a class whose instances are never shared between threads, indicated with the annotation thread_local on the class declaration. This type of class requires no synchronization on field accesses, and a class may be thread local only if:
Program Size (lines) Number of annotations User time (hours) Hashtable 434 46 1 Vector 440 15 0.5 Ambit 4,500 37 3 WebL 20,000 421 16The large number of annotations in Hashtable may be attributed to the use of type parameters which require an annotation on each reference to a parameterized type name. The two larger examples required approximately 20 annotations per thousand lines of code. One race was found in the Vector class, and several races were found in the WebL code.
The most important direction for future work is to reduce the annotation requirements. We are currently exploring better annotation inference algorithms and the possibility of using feedback from dynamic tools to help infer annotations. There are also additional features to implement, such as reader-writer locks and parameterized methods.
[2] Cormac Flanagan and Martin Abadi. Object Types against Races. Proceedings of CONCUR 99, August, 1999.
[3] Thomas Kistler and Johannes Marais. WebL - a programming language for the Web. In Computer Networks and ISDN Systems, Volume 30, pages 259-270. Elsevier, April 1998. Also appeared as SRC Technical Note 1997-029.
[4] K. Rustan M. Leino, James B. Saxe, and Raymie Stata. Checking Java programs via guarded commands. Technical Note 1999-002, Compaq Systems Research Center, Palo Alto, CA, May 1999. Also appeared in Formal Techniques for Java Programs, workshop proceedings. Bart Jacobs, Gary T. Leavens, Peter Muller, and Arnd Poetzsch-Heffter, editors. Technical Report 251, Fernuniversitat Hagen, 1999.
[5] Stefan Savage, Michael Burrows, Greg Nelson, Patrick Sobalvarro, and Thomas E. Anderson. Eraser: A dynamic data race detector for multi-threaded programs. ACM Transactions on Computer Systems (TOCS), 15(4):391-411, November 1997. Also appeared in Proceedings of the Sixteenth ACM Symposium on Operating System Principles, October 5-8, 1997, St. Malo, France, Operating System Review 31(5), ACM Press, 1997, ISBN 0-89791-916-5, pp 27-37.