July 2005
[ jonas ] 10:18, Wednesday, 27 July 2005

Writing multi-threaded applications in Java can as you know be quite tricky, and if not done correctly, one can easily end up with deadlock situations.

For example, take a look at this little code snippet. This piece of Java code implements a Node, which has three methods (that is of our interest):

Object get() , void set(Object o) and void swap(Node n)

Here's the implementation:

public class Node {
     private Object value;
     public synchronized Object get() {
          return value;
      }
     public synchronized void set(Object value) {
          this.value = value;
      }
     public synchronized void swap(Node n) {
          Object tmp = get();
          set(n.get());
          n.set(tmp);
      }
    ... // remaining methods omitted 
}

This class might look okay, but it actually suffers from potential deadlock. For example what happens if, one thread T1 invokes n1.swap(n2) while an other one T2 concurrently invokes n2.swap(n1) ?

Thread T1 acquires the lock for node n1, and thread T2 acquires the lock for node n2. Thread T1 now invokes n2.get() (in the swap(..) method). This invocation now has to wait since node n2 is locked by thread T2, this while the reverse holds for thread T2 (e.g. it needs to wait for node n1 which is locked by thread T1). Which means that we have a deadlock.

This program is however easily fixed by using the technique of totally ordering all objects in the system.

public class Node {
     private Object value;
     public synchronized Object get() {
          return value;
      }
     public synchronized void set(Object value) {
          this.value = value;
      }
     private synchronized void doSwap(Node n) {
          Object tmp = get();
          set(n.get());
          n.set(tmp);
      }
     public void swap(Node n) {
          if (this ==  n) {
               return;
           } else if (System.identityHashCode(this) < System.identityHashCode(n)) {
               doSwap(n);
          } else {
              n.doSwap(this);
          }
      }
    ... // remaining methods omitted 
}

In this program, all locks will be acquired in increasing order, guaranteed to be the same for all threads, and thereby avoiding deadlock situations.

[ jonas ] 12:07, Monday, 18 July 2005
As I mentioned in my previous blog entry, the synchronized block is currently not a supported join point in AspectJ 5 (or in any other AOP framework):
Currently you can for example pick out a call to a method that is declared as being synchronized and you can pick out calls to Thread:: notify()/notifyAll()/wait(). Meaning that being able to pick out synchronized blocks are the only missing piece left in order to completely control thread management and locking in Java. The actual bytecode modifications needed to make this work would be fairly simple, but capturing the correct semantics in a good language design would probably be a lot trickier.

Well, I'm not a language designer, but I think the problem is interesting so I will spend some time discussing it anyway.

In bytecode, a synchronized block is represented as a MONITOR ENTRY and a MONITOR EXIT bytecode instruction pair (however these are not required to be paired). The first natural approach would to let these two bytecode instructions be join points and treat them similar to field access and modification (PUTFIELD and GETFIELD bytecode instructions), meaning simply pick out (and intercept) this single bytecode instruction.

Just to clarify what I mean, here is an example of how the syntax for the above given semantics could look in the AspectJ pointcut expression language:

lock(Type t) && withincode(* Foo.bar(..)) && args(t)

unlock(Type t) && withincode(* Foo.bar(..)) && args(t)

Let us take a look at a synchronized block and how it would be affected:

synchronized(obj) {
     // body
}

In bytecode to this is equivalent to (pseudo code):

MONITOR ENTRY // lock on obj
    // body
MONITOR EXIT

If we now add around advices to these join points then we could for example get (pseude code):

try {
     // a call to the around advice, which calls the lock manager
     aroundAdvice1(obj) --> myLockManager.acquire(obj); 
 
     // body
 
} finally {
     aroundAdvice2(obj) --> myLockManager.release(obj);
}

This approach would give you the possibility to completely control how locking is done in Java (including the possibility of enhancing or completely screw up the Java Memory Model (JMM)). On the other hand it does not allow you to pick out the actual code block that is synchronized (is this something that we want?) . Therefore this approach is perhaps not intuitive, since in Java source code, what we see is not lock acquisition and release but a code block that is guaranteed to be synchronized. So now let's try to approach this problem from the perspective of source code.

Since AspectJ (and AspectWerkz) already has limited support for the synchronized keyword by allowing calls to methods declared as being synchronized to be matched, let us take a look at the semantics for this join point.

Here's a simple method that is defined as being synchronized:

public synchronized void doStuff() { 
     // body
}

What this actually means is:

public void doStuff() { 
     synchronized(this) {
          // body
      }
}

The options we have of picking up this synchronized code block (wrapped up in the doStuff() method) is by using a

execution(synchronized void *.doStuff())
or
call(synchronized void *.doStuff())
pointcut.

Using the execution pointcut, the above method body would be transformed to (pseudo code):

synchronized(this) {
     // the advice has option of invoking the original body
     myAroundAdvice(this) 
}

I.e. only synchronized body is matched and intercepted.

If we are using the call pointcut, the same code snippet would be transformed to (pseudo code):

...
// the advice has the option of invoking the original synchronized block
myAroundAdvice(this) 
...

I.e. the whole synchronized block, including the locking is matched (and intercepted). (This is conceptionally true, in regards to the locking, which is easy to see if you think of the doStuff() method as being inlined.)

I will not go into much detail about how these semantic differences should be expressed in the pointcut language here (this post is too long already). But for example, the last discussion would require the possibility of making a distinction between picking out a code block inclusively and exclusively (i.e. picking out the whole code block, or just the body of the code block), which could be expressed something like this:

// pick out synchronized block including the locking
block-inclusive(synchronized(Type t)) && withincode(* Foo.bar(..)) && args(t)

// pick out only the synchronized block's body, not the locking
block-exclusive(synchronized(Type t)) && withincode(* Foo.bar(..)) && args(t) 

(These "block" pointcut descriptors can of course be used in any kind of block, loops, conditional statements etc., if/whenever they are supported in AspectJ.)

To sum up, the questions are: Do we want the power of intercepting the whole locking mechanism (but not the synchronized body), or is it better to follow the semantics we have for a synchronized method? Which approach addresses the use cases we want? Is the most intuitive? Is more orthogonal?

Thoughts? Comments? Ideas?