Singletons in Java

In this quick tutorial, we'll look at two of the most popular ways to implement singletons in Java.

2. Class Based Singleton

The most common approach is to create a singleton by creating a regular class and checking that it has:

  • private constructor;

  • a static field containing its only instance;

  • static factory method to obtain an instance.

We will also add the property info for later use. So our implementation would look like this:

public final class ClassSingleton {

    private static ClassSingleton INSTANCE;
    private String info = "Initial info class";
    
    private ClassSingleton() {        
    }
    
    public static ClassSingleton getInstance() {
        if(INSTANCE == null) {
            INSTANCE = new ClassSingleton();
        }
        
        return INSTANCE;
    }

    // getters and setters
}

Although this is a common approach, it is important to note that it may be problematic in multi-threaded scenarioswhich is the main reason for using singletons.

Simply put, this can result in more than one instance appearing, which violates the basic principle of the pattern. Our next approach solves these problems at the root level.

3. Enum Singleton

Moving on, let's discuss another interesting approach, which is to use enumerations:

public enum EnumSingleton {
    
    INSTANCE("Initial class info"); 
 
    private String info;
 
    private EnumSingleton(String info) {
        this.info = info;
    }
 
    public EnumSingleton getInstance() {
        return INSTANCE;
    }
    
    // getters and setters
}

With this approach, serialization and thread safety are guaranteed by the enum implementation itself, which internally ensures that only a single instance is accessible, correcting the problems noted in the class-based implementation.

4.Usage

To use ClassSingletonwe just need to get the instance statically:

ClassSingleton classSingleton1 = ClassSingleton.getInstance();

System.out.println(classSingleton1.getInfo()); //Initial class info

ClassSingleton classSingleton2 = ClassSingleton.getInstance();
classSingleton2.setInfo("New class info");

System.out.println(classSingleton1.getInfo()); //New class info
System.out.println(classSingleton2.getInfo()); //New class info

Concerning EnumSingletonthen it can be used like any other Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance();

System.out.println(enumSingleton1.getInfo()); //Initial enum info

EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance();
enumSingleton2.setInfo("New enum info");

System.out.println(enumSingleton1.getInfo()); // New enum info
System.out.println(enumSingleton2.getInfo()); // New enum info

5. Pitfalls

Singleton is a deceptively simple design pattern, and there are several common mistakes a programmer can make when creating a singleton.

There are two types of problems with singletons:

5.1. Existential problems

Conceptually, a singleton is a type of global variable. In general, we know that global variables should be avoided, especially if their states are mutable.

We're not saying you shouldn't use singletons; we believe there may be more efficient ways to organize code.

If the implementation of a method depends on a singleton object, why not pass it as a parameter? In this case, we explicitly show what the method depends on. As a result, we can easily simulate these dependencies (if necessary) during testing.

For example, singletons are often used to convey application configuration data (such as storage connections). If they are used as global objects, it becomes difficult to select a configuration for the test environment.

Therefore, when running tests, test data gets into the production database, which is unacceptable.

If we need a singleton, we can consider delegating its instantiation to another class, a kind of factory, which should take care that there is only one instance of the singleton in play.

5.2. Implementation problems

Although singletons seem fairly simple, there are a number of problems that can arise when implementing them. All of them lead to the fact that we may end up with more than one instance of a class.

Synchronization

The private constructor implementation we presented above is not thread safe. It works well in a single-threaded environment, but in a multi-threaded environment, synchronization techniques should be used to ensure atomicity of operations:

public synchronized static ClassSingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new ClassSingleton();
    }
    return INSTANCE;
}

Pay attention to the keyword synchronized in the method declaration. The method body contains several operations (comparison, instantiation, and return).

In the absence of synchronization, there is a possibility that two threads alternate executions in such a way that Eq. INSTANCE == null is estimated as true for both threads, and as a result two instances are created ClassSingleton.

Synchronization can have a significant impact on performance. If this code is called frequently, you should speed it up using various techniques such as lazy initialization or double check lock (keep in mind that this may not work as expected due to compiler optimizations). You can read more about this in the article “Double-Checked Locking with Singleton“.

Multiple Instances

There are a few other problems with singletons that are related to the JVM itself – they can lead to us getting multiple instances of a singleton. These are fairly subtle issues, and we'll give a brief description of each:

  1. The singleton is supposed to be unique for each JVM. This can be a problem for distributed systems or systems whose internal design is based on distributed technologies.

  2. Each class loader can load its own version of a singleton.

  3. A singleton can be garbage collected if no one else has a reference to it. This issue does not cause multiple singleton instances to exist at the same time, but when recreated, the instance may be different from its previous version.

6. Conclusion

In this short article, we looked at how to implement the Singleton pattern using only Java. We learned how to ensure consistency and how to use these implementations.

The full implementation of these examples can be found on GitHub.

The material was translated ahead of the launch online course “Java QA Engineer. Basic”.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *