Singleton Locks
Singleton is the only type of Session Beans that admits concurrent access. This way bottlenecks are avoided. There are two manners of synchronizing access, bean synchronization and container synchronization. Default is container managed synchronization. We spare a few words on container managed synchronization.
Introduction
Container managed synchronization realizes readers-writes problem. You can designate methods as read methods or write methods using @Lock annotation
@Singleton //all business and timeout methods become read methods by default @Lock(LockType.READ) public class Synch { private int val; //this is read method due to class level configuration public int getVal() { return val; } //this is write method @Lock(LockType.WRITE) public void setVal( int val ) { this.val = val; } //other read method with explicite configuration @Lock(LockType.READ) public String getSmth() { return "smth"; } }
Default value is @Lock(LockType.WRITE).
Readers are threads or clients invoking read methods, writers are threads or clients invoking write methods. Only multiple readers or a single writer can access Singleton at one time, others must wait. For example, if a writer accesses Singleton than all other writers and readers wait until the writer leaves Singleton.
EJB Container is responsible to not starve any thread. Application providers are responsible to avoid bottlenecks in applications.
Reentrence
Reentrence happens when Singletons calls itself via container. It means when Singleton code calls other business method in this Singleton. Example:
@Singleton public class SynchSing { @Resource private SessionContext ctx; @Lock(LockType.READ) public void test1(){ //this is not reentrance, it is local method call targetMethod(); //1 } @Lock(LockType.READ) public void test2(){ //this is reentrance, invocation is via business interface ctx.getBusinessObject(SynchSing.class).targetMethod(); //2 } @Lock(LockType.WRITE) public void targetMethod(){} }
Reentrance from a read method to a write method is forbidden, in such case IllegalLoopbackException is thrown. I.e. invocation //1 is not reentrance and will succeed with a READ lock, invocation //2 is reentrance and will throw an exception. Excerpt from EJB 3.1 specification:
Special locking semantics apply to loopback calls on Singletons with container-managed concurrency.
If a loopback call occurs on a Singleton that already holds a Write lock on the same thread :
• If the target of the loopback call is a Read method, the Read lock must always be granted immediately, without releasing the original Write lock.
• If the target of the loopback call is a Write method, the call must proceed immediately, without releasing the original Write lock.
If a loopback call occurs on a Singleton that holds a Read lock on the same thread ( but does not also hold a Write lock on the same thread ) :
• If the target of the loopback call is a Read method, the call must proceed immediately, without releasing the original Read lock.
• If the target of the loopback call is a Write method, a javax.ejb.IllegalLoopbackException must be thrown to the caller.
Actually there are two ways: first is to try to upgrade read lock to write lock if needed, second is to forbid lock upgrading. EJB 3.1 specification follows the second way. It means that if in a chain of EJB invocations Singleton is invoked at least twice and at least one invocation is write-lock then JEE server must detect such situation, treat it as an error and throw an exception. Reentrence occurs not only when Singleton directly calls itself but also when it calls itself indirectly.
Deadlocks
This mechanism prevents deadlocks. If two clients invokes Singleton and at least one invocation is write-lock then there is no threat of a deadlock, the invocations should be serialized and each of them should complete in reasonable time amount. Assume that an application realize two business processes. Each process consists of a chain of EJB invocations such that invocations of Singleton appear twice, first as read-lock, then as write-lock. Now consider the following scenario where IllegalLoopbackException is not thrown and upgrading lock mechanism is used instead:
- Both processes start
- Both processes invoke Singleton as read-lock
- Both processes attempt to invoke Singleton as write-lock
- A deadlock appears as both processes await for the other one to release a read lock
Notice that lock upgrading lock mechanism would not solve the problem. Moreover, upgrading lock mechanism admits such deadlocks, it makes application provider responsibility to detect deadlocks. It does not mean that EJB Container, that prevents reentrence with lock upgrade, lets application provider off avoiding deadlocks. It just means that EJB Container protects itself against application errors.
Distributed environment
JEE Server is obligated to detect reentrence but only if EJB invocations are executed within a single JVM. Consider the following scenario:
- Client is located in server 1, client invokes local Singleton as read-lock
- Singleton invokes remote EJBean located in server 2
- Remote EJBean invokes Singleton located in server 1 as write-lock
- EJB Container does not know that both invocations of Singleton are in a single chain of invocation, does not treat this situation as reentrance, from Container point of view there are two different clients, obviously this is deadlock
You can treat Singleton locks are thread level, i.e. an information that a client holds read or write lock on Singleton is bounded to a thread. If a thread is switched then an information about locks is lost. Keep in mind that JEE Server is not obligated to solve a diamond problem, it is similar as in this case.
Let us consider a distributed business process that uses Singletons directly or indirectly. It seems that it is a good practice to put Singleton in leaves of process if Singleton admits write-lock invocations.