You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 2 Next »

The Java language facility for the annotation can be useful for the documentation programmer design intent about the concurrency properties of code. Annotation is a mechanism for associating a meta-tag with a program element and allowing the compiler, tools, or the VM to examine it. Several annotations are available to help with the documentation of thread-safety, or the lack thereof, of Java code.

Obtaining concurrency annotations

There are two sets of concurrency annotations that are freely available and licensed for use in any code. The first is the four annotations described in Java Concurrency in Practice (JCIP hereafter) [Goetz 06] which can be downloaded from jcip.net (jar, javadoc, source). The JCIP annotations are released under the Creative Commons Attribution License.

The second, larger, set of concurrency annotations is available from and supported by SureLogic. The SureLogic annotations are released under the The Apache Software License, Version 2.0 and can be downloaded from surelogic.com (jar, javadoc, source). These annotations can be verified by the SureLogic JSure tool but are useful for code documentation purposes without the tool. The SureLogic annotations include the JCIP annotations because they are also supported by the JSure tool (the tool also supports use of the JCIP Jar directly as well).

To use the annotations you download and add one or both of the above Jar files to your code's build path. The use of these annotations to document thread-safety is sketched below.

Documenting intended thread-safety

JCIP provides three class-level annotations to describe the programmer's design intent with respect to thread-safety. In this section we describe these annotations and provide examples of their use (much of the material below is also in the Javadoc for the annotations.)

@ThreadSafe The class to which this annotation is applied is thread-safe. This means that no sequences of accesses (reads and writes to public fields, calls to public methods) may put the object into an invalid state, regardless of the interleaving of those actions by the runtime, and without requiring any additional synchronization or coordination on the part of the caller.

Example: The Aircraft class declares that it is thread-safe as part of its lock policy documentation. This class protects the x and y fields using a ReentrantLock. The @Region and @RegionLock annotations (described below) document the precise locking policy that the promise of thread-safety is based upon.

 @ThreadSafe
 @Region("private AircraftState")
 @RegionLock("StateLock is stateLock protects AircraftState")
 public class Aircraft {
   private final Lock stateLock = new ReentrantLock();
   ...
   @InRegion("AircraftState")
   private long x, y;
   ...
   public void setPosition(long x, long y) {
     stateLock.lock();
     try {
       this.x = x;
       this.y = y;
     } finally {
       stateLock.unlock();
     }
   }
   ...
 }

Even if one or more @RegionLock or @GuardedBy annotations has been made to document the locking policy of a class this annotation can help to clarify that the overall class is thread-safe.

@Immutable The class to which this annotation is applied is immutable. This means that its state cannot be seen to change by callers, which implies that (1) all public fields are final, (2) all public final reference fields refer to other immutable objects, and constructors and methods do not publish references to any internal state which is potentially mutable by the implementation. Immutable objects may still have internal mutable state for purposes of performance optimization; some state variables may be lazily computed, so long as they are computed from immutable state and that callers cannot tell the difference. Immutable objects are inherently thread-safe; they may be passed between threads or published without synchronization.

Example: The immutable Point class below is considered thread-safe.

 @Immutable
 public class Point {
 
   final int f_x;
   final int f_y;
 
   public Point(int x, int y) {
     f_x = x;
     f_y = y;
   }
 
   public int getX() {
     return f_x;
   }
 
   public int getY() {
     return f_y;
   }
 }

@NotThreadSafe The class to which this annotation is applied is not thread-safe. This annotation primarily exists for clarifying the non-thread-safety of a class that might otherwise be assumed to be thread-safe, despite the fact that it is a bad idea to assume a class is thread-safe without good reason.

Example: Most of the collection implementations provided in java.util are not thread-safe. This could be documented for java.util.ArrayList, for example, as shown below.

 package java.util;
 
 @NotThreadSafe
 public class ArrayList extends ... {
   ...
 }

Documenting lock policies

"For each mutable state variable that may be accessed by more than one thread, all accesses to that variable must be preformed with the same lock head. In this case, we say that the variable is guarded by that lock." [Goetz 06, pg 28] Therefore, the critical information to document about any lock policy is which lock is used to protect each shared, mutable variable. JCIP provides the @GuardedBy annotation for this purpose while SureLogic provides the @RegionLock annotation. The use of both annotations is discussed in this section.

@GuardedBy The field or method to which this annotation is applied can only be accessed when holding a particular lock, which may be a built-in (synchronization) lock, or may be an explicit java.util.concurrent.Lock.

Example: The below class implements a movable point with a capability to remember (i.e., memo) its past locations.

@ThreadSafe
public class MovablePoint {
 
  @GuardedBy("this") double xPos = 1.0;
  @GuardedBy("this") double yPos = 1.0;
  @GuardedBy("itself") static final List memo = new ArrayList();
 
  public void move(double slope, double distance) {
    synchronized (this) {
      xPos = xPos + ((1 / slope) * distance);
      yPos = yPos + (slope * distance);
     }
  }

   public static void memo(ex1 value) {
    synchronized (memo) {
      memo.add(value);
    }
  }
}

The @GuardedBy annotations on the xPos and yPos indicate that access to these fields is protected by holding a lock on this (as is done in the move method which mutates these fields). The @GuardedBy annotation on the memo list indicates that a lock on the ArrayList object protects its contents (as is done in the memo method).

A problem with the @GuardedBy annotation is that it does not make it clear that there is a relationship between the two fields in the example above. This limitation can be overcome by using the SureLogic @RegionLock which we now discuss.

@RegionLock Declares a new region lock for the class to which this annotation is applied. This declaration creates a new named lock that associates a particular lock object with a region of the class. The region may only be accessed when the lock is held.

Examples: A locking policy, named SimpleLock, that indicates that synchronizing on the instance protects the all of the instance's state.

 @RegionLock("SimpleLock is this protects Instance")
 class Simple { ... }

Unlike the use of @GuardedBy, the @RegionLock annotation allows the program to give an explicit, and hopefully meaningful, name to the locking policy.

In addition to naming the locking policy, the @Region annotation allows a name to be given to the region of state that is being protected. This makes it clear that the state belongs together with respect to the locking policy. This is demonstrated in the examples below.

A locking policy, named StateLock, that indicates that locking on the java.util.concurrent.Lock stateLock protects the named region, AircraftPosition, that includes all of the mutable state used represent the position of the aircraft.

 @Region("private AircraftPosition")
 @RegionLock("StateLock is stateLock protects AircraftPosition")
 public class Aircraft {
   private final Lock stateLock = new ReentrantLock();

   @InRegion("AircraftPosition")
   private long x, y;

   @InRegion("AircraftPosition")
   private long altitude;
   ...
   public void setPosition(long x, long y) {
     stateLock.lock();
     try {
       this.x = x;
       this.y = y;
     } finally {
       stateLock.unlock();
     }
   }
   ...
 }

Construction of shared, mutable objects

Typically, object construction is considered an exception to the locking policy. Why? This is because objects are thread-confined when they are constructed. They are confined to the thread that invoked the new expression to construct the object. Subsequently, the object is safely published to other threads. At issue, is that the object does not become shared until the thread that invoked the new expression expects it to. One approach is discussed in CON14-J. Do not let the "this" reference escape during object construction and can be expressed with the @Unique("return") annotation.

 @RegionLock("Lock is this protects Instance")
 public class Example {
 
   int x = 1;
   int y;
 
   @Unique("return")
   public Example(int y) {
     this.y = y;
   }
   ...
 }

In the above example the @Unique("return") documents that the object returned from the constructor will be a unique reference.

Documenting thread-confinement policies

Sutherland and Scherlis in Composable Thread Coloring propose annotations that can document thread-confinement policies. Their approach is designed to allow verification of the annotations with the as written code.

Example: Expression, using the annotations proposed by Sutherland and Scherlis, of the design intent that a program has at most one AWT event dispatch thread, as many Compute threads as it wishes, and that the Compute thread is forbidden to handle AWT data structures or events.

@ColorDeclare AWT, Compute
@IncompatibleColors AWT, Compute
@MaxColorCount AWT 1

For more detail refer to the paper referenced above.

Documenting wait-notify protocols

"A state-dependent class should either fully expose (and document) its waiting and notification protocols to subclasses, or prevent subclasses from participating in them at all. (This is an extension of "design and document for inheritance, or else prohibit it" [EJ Item 15].) At the very least, designing a state-dependent class for inheritance requires exposing the condition queues and locks and documenting the condition predicates and synchronization policy; it may also require exposing the underlying state variables. (The worst thing a state-dependent class can do is expose its state to subclasses but not document its protocols for waiting and notification; this is like a class exposing its state variables but not documenting its invariants.)" [Goetz 06, pg 395]

Wait-notify protocols should be documented. Currently, we are not aware of any annotations for this purpose.

  • No labels