Singleton Pattern: The Good, the Bad, and the Ugly
The singleton pattern is one of the most well-known design patterns... wait, or is it an anti-pattern? See how to implement the singleton in Java EE, and look at some of the pros and cons.
Join the DZone community and get the full member experience.
Join For FreeOK, let’s have a look at our first pattern.
The singleton pattern is one of the most well-known design patterns and is used extensively by frameworks such as Spring, pool managers, and loggers.
If you read the GoF book, it’s about having a single instance of an object in the JVM which is only ever instantiated once within the life-cycle of the application. Once created it is not normally destroyed until the application terminates. Why do you need this? The GoF motivation was that heavy-weight objects you would not want to create because they are expensive to have around, are created by the singleton.
If you have read any further in the GoF book, implementing the singleton pattern is quite non-trivial. You have to start with some pretty unnatural constructs like private constructors, double locking and the like, and in the end, you still didn’t get thread safety. You need to think about thread safety as it’s a single instance being shared across the JVM and across multiple threads.
Java EE offers an elegant and easy way to implement the singleton pattern.
Singleton Pattern in Code
@DependsOn("DatabaseConnectionBean")
@Startup
@Singleton
public class Logger {
@PostConstruct
void constructExpensiveObject() {
// Expensive construction
}
}
@Inject
Logger logger;
@Singleton
The creation of the singleton class is done by the container and the container knows to create only one instance because the class is annotated with the @Singleton stereotype annotation. By default, this object is read locked. Access to it is serialized, so you don’t have to worry about thread safety at all. So if two threads attempt to access the instance they will be forced into serialized access.
The container creates an instance of the Logger class and will inject the same instance wherever it finds an injection point. Injection points are annotated @Inject.
@PostConstruct
Let’s get to the expensive construction part. Remember this was one of the principle motivators of the design pattern and why you would want to have the singleton in the first place. We do this by adding the @PostConstruct annotation to the method that constructs the object. This is invoked at application start up.
@Startup
By default the singleton bean is initialized lazily: it won’t be created until first use. Normally this is sufficient for most use case scenarios; however, you may want the singleton bean to perform application start up tasks in which case you must annotate the bean with @Startup: something that is not elegantly available in the classical implementation of this pattern. The container must ensure that the bean is initialized before it delivers client requests.
@DependsOn
The instantiation of your bean may depend on the initialisation of other beans. We can specify the bean on which we depend.
Conclusions so far
Already we have seen how the Java EE implementation of the singleton pattern is markedly different to its classical implementation and requires substantially less code to achieve the same results. You have been given more control over when the bean is instantiated, either at application start up or on first use, and you can specify its dependency on other beans successful instantiation. These new features enhance the behaviour and performance of the singleton bean.
But we can go further.
Now let’s look at how Java EE gives you even greater control over the pattern’s behaviour.
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class Logger {
@AccessTimeout(value = 30, unit=TimeUnit.SECONDS)
@Lock(LockType.WRITE)
public void addMessage(String message) {}
@Lock(LockType.READ)
public String getMessage() {}
}
Remember that I mentioned that the singleton bean is thread-safe by default as concurrency is managed by the container. Well, Java EE offers two types of concurrency management: container managed and bean managed concurrency. By default the container manages the concurrency, removing the need to implement the usual concurrency solutions.
@ConcurrentManagement
However, we are given the option to manage it ourselves by choosing bean managed concurrency. Add the annotation ConcurrencyManagementType.BEAN to the class definition.
We still need to be careful with method access as our bean is exposed to a concurrent environment. Two lock types control access to the bean's business method: WRITE and READ.
@Lock
Methods annotated WRITE, lock to other beans while being invoked. Methods that affect change will be annotated this way. Methods annotated READ allow concurrent access.
In this code snippet, a call to the getMessage method will be forced to wait until the addMessage method completes. This may result in a ConcurrentAccessTimeoutException if the addMessage method does not complete within the specified timeout period.
@AccessTimeout
The timeout period can be configured with an annotation either at the class level or the method level.
The Good, the Bad, and the Ugly
The Good
Java’s EE manner of implementing the Singleton pattern has reduced substantially the lines of boilerplate code required to achieve a tread-safe Singleton. Not only that but it adds features to our singleton that it allows to be instantiated on application start-up or on first invocation. We can make its existence depend on the successful construction of another bean.
By convention, the singleton has container-managed concurrency but we can take back control and manage it ourselves we can even specify an access timeout value.
The Bad
Overuse of singletons can cause problems with your application, as can the overuse of anything. Lazy loading will cause delays on first use while eager loading may cause memory problems. An instance might be created on start-up but never used or garbage collected using up memory resources.
The Ugly
The singleton pattern is often considered an anti-pattern and should be used for niche use-case scenarios. It would be smarter to use a stateless session bean.
Published at DZone with permission of Alex Theedom, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments