One of the long-standing limitations of the EJB component model has been the inability to easily share application-scoped state. The EJB 3.1 Singleton component solves this problem.
A Singleton is a new kind of Session bean that is guaranteed to be instantiated once per application (per server instance). A Singleton bean instance lives for the duration of its associated container. Singletons have much in common with Stateful and Stateless Session beans :
- Support for Container Managed Transactions (CMT) and Bean Managed Transactions (BMT)
- Ability to expose multiple independent client views ( No-interface, Local, Remote, Web Service )
- EJB reference based client programming model
- Access to container services for injection, resource manager access , timers, etc.
Here's a simple Singleton that provides some read-only configuration data :
1: @Singleton
2: public class SharedConfig {
3:
4: private ConfigData config;
5:
6: @PostConstruct
7: private void init() {
8: // initialize configuration data
9: config = ...;
10: }
11:
12: public int getPropertyA() {
13: return config.propertyA;
14: }
15: }
It's common for such shared state to be very large and have significant setup costs. Storing it within a singleton guarantees efficient use of memory and avoids the overhead of redundant initialization.
Client access is easy -- just acquire a Singleton EJB reference through injection or lookup :
1: @Stateless
2: public class FooBean {
3:
4: @EJB
5: private SharedConfig configBean;
6:
7: public void foo() {
8: int propertyA = configBean.getPropertyA();
9: ...
10: }
11: }
Reusing the standard session bean client view for Singletons has the added benefit of allowing access from many kinds of clients, not just other EJB components. This makes it easy to share state between the web components and EJB components in an application.
Concurrency is another important Singleton topic. Singletons are intended to be called by many clients at once. However, the EJB component model has always guaranteed that no more than one thread has access to a particular bean instance at a time. Offering only that threading policy for Singletons would ensure correctness but would be too restrictive from a performance standpoint.
In order to balance these concerns, the container ensures single-threaded access to Singletons by default, but the developer can choose between two additional concurrency policies : container-managed concurrency (CMC) and bean-managed concurrency (BMC).
With CMC, developers use method-level shared/exclusive locks to tell the container when it's OK for multiple callers to have concurrent access to the bean instance. An invocation that can not proceed due to locks is blocked until it can make forward progress or until an optionally-specified timeout is reached. In CMC mode, the container is still in control but the fact that instance-level concurrency can occur boosts performance. The developer is freed from using Java SE level synchronization primitives to protect instance state.
Here's a slightly modified version of the previous Singleton example, this time using CMC :
1: @Singleton
2: public class SharedConfig {
3:
4: private ConfigData config;
5:
6: @Lock(LockType.READ)
7: public int getPropertyA() {
8: return config.propertyA;
9: }
10:
11: @PostConstruct
12: private void init() {
13: // initialize configuration data
14: config = ...;
15: }
16: }
The @Lock(LockType.READ) annotation tells the container that any number of concurrent invocations can access getPropertyA() at the same time. This is the simplest case since this bean has immutable state. If it supported updates, it would look like this :
1: @Singleton
2: public class SharedConfig {
3:
4: private ConfigData config;
5:
6: @Lock(LockType.READ)
7: public int getPropertyA() {
8: return config.propertyA;
9: }
10:
11: @Lock(LockType.WRITE)
12: public void update(...) {
13: // update state
14: ...
15: }
16:
17: @PostConstruct
18: private void init() {
19: // initialize configuration data
20: config = ...;
21: }
22: }
Here, the container guarantees that an invocation on the update() method will only proceed when it can have exclusive access to the bean instance.
In BMC mode, the bean developer has full control over concurrency. The container passes all invocation threads directly through to the bean instance, just as in the Servlet programming model. It's the developer's responsibility to ensure the integrity of the instance state however possible, including through the use of Java SE level synchronization primitives. Here's the previous example using BMC mode :
1: @Singleton
2: @ConcurrencyManagement(ConcurrencyManagementType.BEAN)
3: public class SharedConfig {
4:
5: private ConfigData config;
6:
7: synchronized public int getPropertyA() {
8: return config.propertyA;
9: }
10:
11: synchronized public void update(...) {
12: // update state
13: ...
14: }
15:
16: @PostConstruct
17: private void init() {
18: // initialize configuration data
19: config = ...;
20: }
21: }
This is an oversimplified example of BMC merely intended to show the required annotations and an example of Java SE level synchronization appearing directly in the bean class. Typically the locking would not be applied at the method level since that approach does not offer significant benefit over CMC mode. Much like the tradeoffs associated with using Bean Managed Transactions, Bean Managed Concurrency mode offers finer-grained control at the cost of added code complexity.
That's a brief introduction to Singletons. I'll be writing a follow-up post about how they can provide another piece of previously missing EJB component functionality : application startup and shutdown callbacks.