Introduction
In the previous blog text, I demonstrated some aspects of a Stateless EJB bean. It is guaranteed that each client receives his copy of the bean and that there is no concurrent access. But it is possible, and here there where differences between the application servers, that the same bean instance is reused in different calls.This text will handle the Singleton EJB beans. And at first glance, you just have to change the @Stateless annotation with @Singleton. But there are a few consequences regarding the concurrent access of methods.
With the same set of examples, I’ll explain them to you.
Scope of Singleton
Now it is obvious how long the bean is kept alive. Singleton means that there is only one instance created and reused for all calls.So if you change the annotation on the SomeEJB class to @Singleton, you are ready to go to run the code for scenario1.
@Singleton public class SomeEJB {
The results are now, for GlassFish and WildFly, the same and indicates that we received each time the same instance.click 1 -> value = 1
click 2 -> value = 2
click 3 -> value = 3
For scenario2, we get the following results
click 1 -> value = 1 - 2 - 3 - 4 - 5
click 2 -> value = 6 - 7 - 8 - 9 - 10
click 3 -> value = 11 - 12 - 13 - 14 - 15
So it is very clear that as the name indicates, we have one instance of the bean. In all cases.
Concurrency aspects
Since we now have only one instance, what happens when we access the singleton bean in a concurrent way. What is the default behaviour when multiple client access the bean instance?This we can investigate with the code we wrote for the scenario3. Look at the previous blog text for the actual code.
click -> value : 1 - 2 - 3 - 4 - 5
elapsed time : 10018
As it is a singleton instance, each call increment the counter value. But the interesting figure here is the time it took to complete the test. It took just over 10 seconds to finish. This is the 5 calls multiplied by the 2 seconds delay we have put into the method call.
And if you investigate the code execution more in depth, you should find that the second invocation only starts when the first one is finished.
The call to the first invocation immediate returns due to the @Asynchronous annotation and the Future return value. The for loop in the scenario3() method continues and starts with the second invocation of the Singleton bean method. But this one doesn’t start, and thus also control is not given back to the scenario3 code, until the first invocation is finished.
There is obvious no parallel code execution anymore as we saw in the examples of the Stateless EJB bean.
And it looks look there is an invisible synchronised keyword placed on the method.
Concurrency protection
This default behaviour is specified in the EJB specification to protect the values which are kept in the singleton bean. Since we now have only one instance available, multiple clients access this same instance and thus could potential read partial updated values.The easiest solution was to guarantee that only one client can execute some method in the Singleton bean and that all other invocations must wait until the current one is finished.
In our example we proved it for the same method invocation, but in fact, any method call to the same Singleton bean is placed on hold.
Scaling Singleton beans
This default behaviour is easy for the developer and guarantees the correct concurrent access to the values kept in the Singleton bean but it is obvious not a good situation for the scaling properties of our application when we have a Singleton bean which is access by many clients in a concurrent fashion.In that scenario, we have some additional annotations so that we can handle the concurrency aspects our selves. But of course, it gives the programmer a greater responsibility to do the correct thing.
The @ConcurrencyManagement annotation can be placed on the class and here we can indicate that the bean itself is responsible for correct multithread access handling.
@Singleton @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class SomeEJB { private long counter = 0; @Asynchronous public Future<Long> asyncIncAndGet() {...
}
}
The other annotation we can use is the @Lock annotation (in case we do container managed concurrency management). And there exists 2 types of locks, a READ lock and a WRITE lock.You can obtain a READ lock, and thus execute the method, when no other thread has a WRITE lock at that time. If there is a WRITE lock taken, your invocation has to wait until the lock is released.
To obtain a WRITE lock, no other threads should hold a READ or WRITE lock on that Singleton bean.
So if you change the code of the SomeEJB class to include the @ConcurrencyManagment annotation, you can have the following results if you run scenario3 again.
click -> value : 4 - 4 - 4 - 4 - 4
elapsed time : 2002
The elapsed time indicates now that we have again parallel execution of the method but the values we receive for the counter are not correct.
Optimal Singleton pattern
So how should we use the @Lock annotation properly?You should place the @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) and @Lock(LockType.READ) on the class definition.
This guarantees that we can access all method in a parallel way in the Singleton Bean.
And those methods that change the value of some instance variables we keep in the Singleton bean, should be annotated with
@Lock(LockType.WRITE)
Then we know that no other thread will be reading the variables and we can safely change the values without the risk there will be read of some wrong values.
Conclusion
You can just annotate a POJO with @Singleton and there will be only one instance of the bean for your whole application. By default however, the server guarantees that no threads are accessing some method of the bean.This is a very safe way but also a very slow situation as performance can degrade because clients have to wait to access the bean.
With the use of @ConcurrencyManagement and @Lock annotations we can create the optimal situation where we can have concurrent access if we only read values and make sure that no other thread is reading or writing values when we make changes.
Thanks to @rmannibucau to point out that I mixed up the ConcurrencyManagementType.CONTAINER and .BEAN values.
ReplyDeleteI updated the 'Optimal Singleton pattern' section to correct my error.
Hi Rudy, nice article.
ReplyDeleteYou can still use @ConcurrencyManagement type BEAN and have an "Optimal Singleton" but in this config you have to control the write locks like eg. using a ConcurrentHashMap or synchronized keyword. I particularly prefer type BEAN cause i have more (fine grained)control, eg i can lock inside a method. Does it make sense for you?
Hi Rafael,
DeleteYes you are correct. Using ConcurrencyManagement type BEAN requires that the programmer manages concurrency like using ConcurrentHashMap.
You have more fine grained control, probably more code and more bugs/issues :) (multi-thread programming can become very tricky).
But yes, that is the ultimate "optimal Singleton"