Support for Shared State Concurrency

Join's support for shared state concurrency is different from Java / C# monitor based model.

Java/C#'s model is based on "monitor", the whole object state (all application data too) is the critical region protected by an object-wide lock, if a synchronized method is called, the lock is held during the execution of method body till the end of body or released explicitly in middle when using synchronized statement, the callings of all other synchronized methods will be blocked till the first calling returns.

In Join, the object-wide lock is only used to protect the synchronization state of the object, embedded in actor / async<> / synch<> members. When a async / synch method is invoked, the lock is only held for checking if any chords are ready to fire (all its messages have arrived) and buffering messages and then it is released. When chord bodies execute (either in calling thread of a synch<> method or a task in thread pool), this lock is not held anymore.

We can explore the implications of this difference in the following directions:
  1. Join's object-wide lock is only held during checking internal synchronization status which is a very short period. Java's object-wide lock is held for executing application code which could be relatively long period if I/O is involved or other objects' methods are invoked.
  1. Join's object-wide lock is not used to synchronize access to application code, instead this synchronization should be specified explicitly in chords or join-patterns. For example, if two methods may access / modify an internal data (data_A) concurrently, we could define an async method data_A_lock() to synchronize the access:

   chord(method1, data_A_lock, chord_body1);
   chord(method2, data_A_lock, chord_body2);
By calling data_A_lock(), data_A is unlocked. Then the calling of method1/chord_body1 and method2/chord_body2 are synchronized regarding to data_A.
  1. Join's synchronization is more fine-grained. In Java/C# model, calling a synchronized method will lock the whole object. For some object, its synchronized methods could be divided into diff groups because they depends on diff resources or states. Theoretically, one method from each of these diff groups should be able to run concurrently without interfering. In Java we cannot do it since the whole object is locked. To do it we need to split the original object into smaller objects, one for each group, to use different locks. This issue can be easily resolved in Join (with one lock) by defining a separate async<> method for each group and diff methods can join with a proper async<> method to acquire the exclusive access to the resource it needs.
  1. Join's model can be thought as a generic contention manager or synchronization framework which can be used to code any customized contention management / synchronization requirements (as shown in many tutorials) and avoid some important cases of dead-locks. For example, in some applications, actions need exclusive access to multiple resources. Traditionally we need first lock all these resources one by one and then perform the action. This is problematic design, since some other actions may need exclusive access to (some of) these resources too. We have to make sure all these resources / locks are acquired in the same order otherwise dead-lock will occur.
In Join, we can resolve this issue by defining an async method (lock1(), lock2(),...)  for each resource and defining chord to join the action method with the set of async methods represents the set of resources it requires:
   chord(action1, lock1, lock2, lock3,..., chord_body);
Please note that when message arrive, Join will check the availablity of all resources (lock1, lock2, ...) in one "atomic" transaction, so the order of how lock1(), lock2(),... are called are not relevant or important anymore.