From the client’s perspective, a Singleton bean always supports concurrent access. In general, a Singleton client does not have to concern itself with whether other clients might be accessing the Singleton at the same time.
From the bean developer’s perspective, there are two approaches for controlling Singleton concurrency behaviour:
- Container-managed concurrency (CMC)
- The container controls concurrent access to the bean instance based on method-level locking metadata.
- Bean-managed concurrency (BMC)
- The container allows full concurrent bean instance access and defers state synchronization responsibility to the bean developer.
Typically Singleton beans will be specified to have container managed concurrency demarcation. This is the default if no concurrency management type is specified. A Singleton bean can be designed to use either CMC or BMC but it cannot use both.
Container Managed Concurrency (CMC)
- In CMC, the container is responsible for controlling concurrent access to the bean instance based on method-level locking metadata.
- Although by default, singletons use container-managed concurrency, the @ConcurrencyManagement(CONTAINER) annotation may be added at the class level of the singleton to explicitly set the concurrency management type:
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class ExampleSingletonBean { … }
- Each business method or timeout method is associated with either a Read (shared) lock or Write (exclusive) lock.
- If the container invokes a method associated with a Read lock, any number of other concurrent invocations on Read methods is allowed to access the bean instance simultaneously.
- If the container invokes a method associated with a Write lock, no other concurrent invocations will be allowed to proceed until the initial Write method’s processing completes.
- A concurrent access attempt that is not allowed to proceed due to locking is blocked until it can make forward progress. Timeouts can be specified via metadata so that a blocked request can be rejected if a lock is not acquired within a certain amount of time.
- If a Singleton invocation is rejected due to lock timeout the ConcurrentAccessTimeoutException is thrown to the client.
- The concurrency locking attributes for the methods of a bean class may be specified on the class, the business methods of the class, or both.
- Specifying the Lock annotation on the bean class means that it applies to all applicable business methods of the class. If the concurrency locking attribute is not specified, it is assumed to be Lock(WRITE) which is the default locking attribute.
- The Lock(READ) and Lock(WRITE) annotations are used to specify concurrency locking attributes.
- A concurrency locking attribute may be specified on a method of the bean class to override the concurrency locking attribute value explicitly or implicitly specified on the bean class.
- A method which is inherited from superclass but not overridden gets the concurrency locking attribute as specified in the superclass or in its method. If no attribute is specified in superclass or in its method the default Lock(WRITE) is applied.
- A method which is inherited from superclass and overridden gets the concurrency locking attribute as specified in the overridden subclass or in its method. If no attribute is specified in subclass or in its method the default Lock(WRITE) is applied.
If the bean class has superclasses, the following additional rules apply:
@Lock(READ) public class SomeClass { public void aMethod () { ... } public void bMethod () { ... } ... } @Singleton public class ABean extends SomeClass implements A { public void aMethod () { ... } @Lock(WRITE) public void cMethod () { ... } ... }
Assuming aMethod, bMethod, cMethod of Singleton bean ABean are methods of business interface ‘A’, their concurrency locking attributes are Lock(WRITE), Lock(READ), and Lock(WRITE) respectively.
Example
- In this example, we are going to create a singleton session bean which maintains a product registry containing product id and price.
- Since the product registry is going to be the same for all clients, Singleton session bean is a good choice.
Create the business interface for the Singleton bean as follows.
package com.ibytecode.business; import javax.ejb.Remote; @Remote public interface ProductRegistry { double getPrice(int id); void setPrice(int id, double price); }
Create the Singleton bean implementation class as follows.
package com.ibytecode.businesslogic; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Lock; import javax.ejb.LockType; import javax.ejb.Singleton; import com.ibytecode.business.ProductRegistry; @Lock(LockType.READ) @Singleton public class ProductRegistryBean implements ProductRegistry { Map<Integer, Double> pdtRegistry; @PostConstruct void initialize() { pdtRegistry = new HashMap<Integer, Double>(); pdtRegistry.put(100, 5000.00); pdtRegistry.put(101, 6000.00); pdtRegistry.put(102, 7000.00); pdtRegistry.put(103, 8000.00); pdtRegistry.put(104, 9000.00); } public double getPrice(int id) { return pdtRegistry.get(id); } @Lock(LockType.WRITE) public void setPrice(int id, double price) { System.out.println("1 -> Before -> " + getPrice(id)); pdtRegistry.put(id, price); System.out.println("2 -> After -> " + getPrice(id)); } @PreDestroy void cleanup() { pdtRegistry = null; } }
- The default locking type for a class or method is Lock(WRITE). In our example, we specified Lock(READ) for the class.
- The method getPrice() has no locking specified so it the gets the locking type specified in the class which is Lock(READ). Because for getPrice() which only reads the value, no synchronization or locking is required.
- But for the method setPrice() which writes the value in the map, we need to synchronize the access so that when multiple clients writes the value it is in done in proper order. Hence we specify the locking type for this method as Lock(WRITE) so that the container can lock this method to prevent concurrent access.
- We also specify the @PostConstruct and @PreDestroy which will initialize and release the map variable.
Create a Java Application Client to access this singleton bean as follows.
package com.ibytecode.client; import javax.naming.Context; import javax.naming.NamingException; import com.ibytecode.business.ProductRegistry; import com.ibytecode.businesslogic.ProductRegistryBean; import com.ibytecode.clientutility.JNDILookupClass; public class ProductRegistryBeanClient implements Runnable{ public static void main(String[] args) { ProductRegistryBeanClient obj1 = new ProductRegistryBeanClient(); for(int i = 0 ; i < 5; i++) { Thread t1 = new Thread(obj1, "T" + i); t1.start(); } } public void run() { ProductRegistry bean = doLookup(); double value = Math.ceil(Math.random() * 10000); bean.setPrice(100, value); } private static ProductRegistry doLookup() { //Refer the example link for complete code . . . . . } }
- We create 5 threads which access the singleton concurrently to set the price for the product id 100.
- Since the setPrice() has Lock(WRITE), the write is done in proper order as seen in the following output on the server.
JBoss Application Console will display the following output:
[stdout] 1 -> Before -> 5000.0
[stdout] 2 -> After -> 3301.0
[stdout] 1 -> Before -> 3301.0
[stdout] 2 -> After -> 5420.0
[stdout] 1 -> Before -> 5420.0
[stdout] 2 -> After -> 5248.0
[stdout] 1 -> Before -> 5248.0
[stdout] 2 -> After -> 8914.0
[stdout] 1 -> Before -> 8914.0
[stdout] 2 -> After -> 410.0
- We use println statements in setPrice() method to see the order, which prints the value of the variable ‘price’ before and after changing.
- But if we comment/remove the line Lock(WRITE) above the setPrice() method, the locking type becomes READ which allows concurrent access so the order may not be synchronized as seen in the following output.
Output without Lock Write:
[stdout] 1 -> Before -> 5000.0
[stdout] 2 -> After -> 9346.0
[stdout] 1 -> Before -> 9346.0
[stdout] 2 -> After -> 2817.0
[stdout] 1 -> Before -> 2817.0
[stdout] 1 -> Before -> 9346.0
[stdout] 2 -> After -> 9932.0
[stdout] 1 -> Before -> 9346.0
[stdout] 2 -> After -> 8033.0
[stdout] 2 -> After -> 7782.0
Bean Managed Concurrency
With Bean Managed Concurrency demarcation, the container allows full concurrent access to the Singleton bean instance. It is the responsibility of the bean developer to guard its state as necessary against synchronization errors due to concurrent access. The bean developer is permitted to use the Java language level synchronization primitives such as synchronized and volatile for this purpose.
Add a @ConcurrencyManagement annotation with the type set to ConcurrencyManagementType.BEAN at the class level of the singleton to specify bean-managed concurrency:
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
@Singleton
public class AnotherSingletonBean { … }