Effective Java with Groovy - Singleton
Singleton is one of the most popular patterns among the ones from the Gang-of-Four design patterns. Many times, we would require just one instance of a class. Say our application connects to exactly one database; then, we would need one instance of the class that manages database connections. If developers try to create multiple instances of that class, it might waste resources such as memory and CPU time. Also, it might introduce potential issues, such as the resource not being released properly while shutting down the application. Hence, it might be a good idea to prevent developers from creating multiple instances of those classes.
While the GoF books recommend that the Singleton pattern is applicable when there must be exactly one instance of the class needed, I would reframe the applicability to - when no more than one instance is needed. Why create a costly object if that is not needed after all?
Java
In Java, we implement the Singleton pattern by
- Making the constructor private (This will ensure it cannot be invoked from outside the class)
- Making the sole instance available through a public static member
The following code snippet shows an example implementation.
public class Manager {
public static final Manager INSTANCE = new Manager();
private Manager() {
}
public void doSomething() {
System.out.println("Well, let me delegate that to a developer.");
}
}
Now, we cannot invoke the constructor of the class Manager
outside the class.
Manager manager = new Manager();
We will get an error saying the constructor Manager()
has private access.
We can use the Singleton class as follows.
Manager manager = Manager.INSTANCE;
manager.doSomething();
Instead of proving the public static member INSTANCE, the instance can be kept private and exposed outside the class via a public factory method.
Groovy
Let's start our Groovy implementation of Singleton by copying the Java implementation and removing the redundant access modifiers.
class Manager {
public static final Manager INSTANCE = new Manager();
private Manager() {
}
void doSomething() {
println("Well, let me delegate that to a developer.");
}
}
Let us see if it is working as expected.
Manager manager = Manager.INSTANCE
manager.doSomething()
You would see it prints "Well, let me delegate that to a developer." on the console.
Can we say our Singleton implementation is correct? Not yet - we need to ensure we cannot call the constrictor.
Manager m1 = new Manager()
Manager m2 = new Manager()
println m1.equals(m2) // false
We can successfully invoke the private constructor and a new instance each time.
We are able to invoke the private constructor/ methods because of the indirection between the caller and the callee, which enables dynamic capabilities in Groovy.
Fortunately, Groovy not only provides a solution but is also simpler than the typical Java solution.
@Singleton
class Manager {
void doSomething() {
println("Well, let me delegate that to a developer.");
}
}
The built-in AST transformation groovy.lang.Singleton
comes to the rescue here. With this version of the code, if you try to invoke the constructor of class MAnager
, you receive an error message saying, "Can't instantiate the Singleton class ". This happens because Groovy customises the generated private constructor as follows.
private Manager() {
if (null != instance ) {
throw new java.lang.RuntimeException('Can\'t instantiate singleton Manager. Use Manager.instance');
}
}
You can use the Singleton class Manager
as follows.
Manager manager = Manager.instance
manager.doSomething()
Note that instance
is the name of the static member through which the sole instance can be accessed. If you wish to customise, it is possible as follows.
@Singleton(property = "INSTANCE")
class Manager {
void doSomething() {
println("Well, let me delegate that to a developer.");
}
}
Manager manager = Manager.INSTANCE
manager.doSomething()
In this case, the sole instance of the Singleton class is created even if no consumers are using it. If you wish to instantiate the class only when the first consumer attempts to get the instance of the Singleton class, then Groovy supports the case with a lazy
attribute as follows.
@Singleton(lazy = true)
class Manager {
void doSomething() {
println("Well, let me delegate that to a developer.");
}
}
Manager manager = Manager.instance
manager.doSomething()
If you are wondering what would happen if two threads simultaneously attempt to access the Singleton (when no instance has been created yet), don't worry. Groovy takes care of properly synchronising the access so that only one thread will create the instance, and the second thread will use the instance created by the first thread that managed to create the instance.
Also, in the code Manager.instance
, it seems like we are accessing a variable, but in fact, we are invoking the method getInstance()
. Refer to this post if you are not familiar with that capability of Groovy.
The following Java code can represent the lazy initialisation implementation generated by Groovy.
private static volatile Manager instance;
public static Manager getInstance() {
if (null != instance ) {
return instance;
} else {
synchronized (Manager) {
if (null != instance ) {
return instance ;
} else {
return instance = new Manager();
}
}
}
}
Note that instance
is declared as volatile. Also, inside the synchronized
block, there is a null check for instance
. This is required to handle the case I mentioned above when two threads invoke the getInstace()
method when the sole instance of Manager
doesn't exist. When the thread that was waiting for its opportunity gets its chance to run, the other thread would have created the Manager
instance, and hence instance
will not be null. On the other hand, making the entire method synchronized
would result in poor performance, as it would serialise access to the sole instance.
Imagine a scenario where you have to switch between the Singleton implementation to be lazy or non-lazy. In Java solution, you will have to modify a lot of code, while in Groovy, it is just an attribute you need to change. Also, implementing lazily initialised Singleton will require in-depth knowledge of Java. In some of the enterprise codebases I worked with, I observed these mistakes in the Singletron implementations.
To learn more about Effective Java with Groovy, refer to the collection of posts here.