This article delves into the Singleton Design Pattern in Java, elucidating its concept and implementation methods. It highlights Singleton’s purpose to ensure a single class instance throughout an application and explores various implementation techniques, including eager, lazy, and Bill Pugh Singleton Initialization. Ideal for developers seeking to deepen their understanding of design patterns in Java. For more details, you can read the full article.

What is Singleton Design Pattern?

Before we delve into the concept of the Singleton Design Pattern, let’s together look at this practical example. In recent years, Lombok has emerged as a deity in the programming world, a fantastic library to reduce boilerplate code. Most coders have also used snippets of code like the ones below.

@Slf4j

public class DashboardController{

    @GetMapping

    public ResponseEntity>> getAll() {

        log.info(“Processing for dashboard”);

        return ResponseEntity.ok(ResponseDto.response(Map.of()));

    }

}

I firmly believe that many Devs also question where this log object comes from? How many instances of the log are created in the application… From what I understand, this log object is created by Lombok through the @Slf4j annotation. And the special thing I want to emphasize here is that there is only one instance of the log object created throughout the entire application. And of course, this instance can be used in any class of the program.

So, ensuring that only one instance of a class is created throughout the entire program and ensuring that all classes can use this instance is the noble and crucial task of the Singleton Design Pattern.

Singleton Design Pattern Implementation

There are several ways to implement a Singleton Design Pattern, but most methods adhere to the following rules:

  • First: The constructor must be private to prevent the instantiation of the class from outside.
  • Second: There must be a public static method to return the instance of the class. This method is commonly named getInstance().
  • Public to ensure other classes can access the instance.
  • Static so other classes can use the getInstance() method without needing to instantiate the object. If one had to instantiate the object to use the getInstance() method, it would defeat the purpose of a Singleton.
  • Third: There must be a private static variable; this is the representation of the Singleton.
  • Private to prevent access from outside.
  • Static because this variable is used within the static getInstance() method.

Now, let’s explore the ways to implement the Singleton Design Pattern together.

Eager Initialization

This is the simplest way to implement a Singleton in the Java programming language.

public class DatabaseConnection{

    private static DatabaseConnection instance = new DatabaseConnection();

    private DatabaseConnection(){ /*hidden constructor*/ }

    private static DatabaseConnection getInstance(){

        return instance;

    }

}

However, the downside of this approach is that the instance is always instantiated during class loading. This can lead to wastefulness since there may be no clients using it. Moreover, this instantiation method cannot apply exception handling.

To address the possibility of handling exceptions during the instance initialization process, let’s look at the Static block initialization solution.

Static Block Initialization

With a static block, we can handle exceptions that occur during the instance initialization process. However, it still does not improve the situation of instantiating the instance upon class loading.

public class DatabaseConnection{

    private static DatabaseConnection instance;

    private DatabaseConnection(){ /*hidden constructor*/ }

    static {

        try{

            instance = new DatabaseConnection();

        }catch(Exception e){

            throw new RuntimeException(“Error”);

        }

    }

    private static DatabaseConnection getInstance(){

        return instance;

    }

}

Lazy Initialization

Lazy initialization allows us to instantiate the instance directly within the getInstance() method. This ensures that the instance is only created when it is actually used by a client.

public class DatabaseConnection{

    private static DatabaseConnection instance;

    private DatabaseConnection(){ /*hidden constructor*/ }

    private static DatabaseConnection getInstance(){

        if(instance != null){

            try{

                instance = new DatabaseConnection();

            }catch(Exception e){

                throw new RuntimeException(“Error”);

            }

        }

        return instance;

    }

}

This method works well in a single-threaded environment. However, in a multi-threading environment, there’s a possibility that multiple threads might call the getInstance() method at the same time. This could lead to more than one instance being created.

The simplest way to resolve this issue is to use the synchronized keyword for the getInstance() method.

public class DatabaseConnection{

    private static DatabaseConnection instance;

    private DatabaseConnection(){ /*hidden constructor*/ }

    private static synchronized DatabaseConnection getInstance(){

        if(instance != null){

            try{

                instance = new DatabaseConnection();

            }catch(Exception e){

                throw new RuntimeException(“Error”);

            }

        }

        return instance;

    }

}

Synchronized ensures that only one thread can access the getInstance() method at a time. However, this also means a reduction in the application’s performance since other threads must wait until the current thread releases the getInstance() method. Moreover, before creating an instance, there are many tasks to do such as validation, logging… these tasks do not necessarily need to be synchronized.

We can improve this by moving synchronized inside the getInstance() method.

private static DatabaseConnection getInstance(){

        if(instance != null){

            synchronized(DatabaseConnection.class){

                if(instance != null){

                    try{

                        instance = new DatabaseConnection();

                    }catch(Exception e){

                        throw new RuntimeException(“Error”);

                    }

                }

            }

        }

        return instance;

    }

Pay attention here, loves, within the synchronized block, we need to check for null one more time to ensure that no thread creates an instance while waiting.

Bill Pugh Singleton

Bill Pugh employs a static nested class and initializes the Singleton’s instance within this nested class. This leverages the JVM’s class loading mechanism to ensure that the instance is created safely when the Singleton class is loaded, and we do not need to use the synchronized keyword, significantly improving performance.

public class DatabaseConnection{

    private DatabaseConnection(){/*hidden constructor */}

    private static class SingletonHelper{

        private static final DatabaseConnection instance = new DatabaseConnection();

    }

    private static DatabaseConnection getInstance(){

        return SingletonHelper.instance;

    }

}

Breaking Singleton

There are two ways to break the structure of a Singleton: Java Reflection and Deserialization.

Using Java Reflection to Break the Structure of a Singleton

public class SingletonTest {

    public static void main(String[] args) {

       DatabaseConnection instanceOne = DatabaseConnection.getInstance();

        DatabaseConnection instanceTwo = null;

        try {

            Constructor[] constructors = DatabaseConnection.class.getDeclaredConstructors();

            for (Constructor constructor : constructors) {

                constructor.setAccessible(true);

                instanceTwo = (DatabaseConnection) constructor.newInstance();

                break;

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        System.out.println(instanceOne.hashCode());

        System.out.println(instanceTwo.hashCode());

    }

}

The hashCode results obtained are completely different. To avoid the structure of a Singleton being broken by Reflection, we can use an Enum to create a Singleton.

public enum EnumSingleton {

    INSTANCE;

    public static void doSomething() {

        // implement TODO

    }

}

Using Deserialization to Break the Structure of a Singleton

We need to implement the Serializable interface in the Singleton class to be able to store its state in a file system and then retrieve it for further processing.

public class DatabaseConnection implements Serializable{

    private static DatabaseConnection instance = new DatabaseConnection();

    private DatabaseConnection(){ /*hidden constructor*/ }

    private static DatabaseConnection getInstance(){

        return instance;

    }

}

However, when we deserialize, it creates a new object of the Singleton class. Clearly, we then have two different objects of the same Singleton class.

public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, 

IOException, ClassNotFoundException {

        DatabaseConnection instanceOne = DatabaseConnection.getInstance();

        ObjectOutput outObj = new ObjectOutputStream(new FileOutputStream(“test.bin”));

        outObj.writeObject(instanceOne);

        outObj.close();

        ObjectInput inObj = new ObjectInputStream(new FileInputStream(“test.bin”));

        DatabaseConnection instanceTwo = (DatabaseConnection) inObj.readObject();

        inObj.close();

        System.out.println(“instanceOne hashCode=”+instanceOne.hashCode());

        System.out.println(“instanceTwo hashCode=”+instanceTwo.hashCode());

    }

}

To handle this situation, we need to implement the readResolve() method in the Singleton class.

public class DatabaseConnection implements Serializable{

    private DatabaseConnection(){/*hidden constructor */}

    private static class SingletonHelper{

        private static final DatabaseConnection instance = new DatabaseConnection();

    }

    private static DatabaseConnection getInstance(){

        return SingletonHelper.instance;

    }

    protected Object readResolve() {

        return getInstance();

    }

}

I would like to conclude the article here. Through this article, I have listed what a Singleton Design Pattern is and the ways to implement a Singleton… I hope to receive many contributions from everyone so that the programming community can continue to develop and, especially, so that future articles can be improved.

contact

Java Development Services

Our dedicated team is adept at implementing sophisticated design patterns, including Singleton, to ensure your software is robust, scalable, and efficient. By choosing Saigon Technology, you leverage a wealth of knowledge and experience in delivering high-quality Java development services.
View Our Offerings arrow-narrow-right.png
Content manager
Thanh (Bruce) Pham
CEO of Saigon Technology
A Member of Forbes Technology Council

Related articles

Java or .NET For Web Application Development
Industry

Java or .NET For Web Application Development

Java or .NET for your web development project? Saigon Technology shares its 12-year expertise. Starting a new IT project can be tough, especially for beginners.
15 Amazing Things You Can Do With JavaScript
Methodology

15 Amazing Things You Can Do With JavaScript

JavaScript lets developers create interactive websites and apps. Use it for server-side programming, games, and art projects.
[Design Pattern] Lesson 04: Factory Design Pattern in Java
Technologies

[Design Pattern] Lesson 04: Factory Design Pattern in Java

The article is part of a series on design patterns. It is a continuation of the design pattern series, with Factory being an extremely useful design pattern for creating objects.
calendar 26 Apr 2024
[Design Pattern] Lesson 05: Builder Design Pattern in Java
Technologies

[Design Pattern] Lesson 05: Builder Design Pattern in Java

Following the Creational patterns, the Builder is an extremely useful design pattern for creating objects. So, what is a Builder and how do you implement one? Through this article, we hope to provide everyone with additional knowledge about the Builder pattern.
calendar 28 Aug 2024
The Essential Guide to Clean Code Practices for JavaScript, React, and Node.js Projects
Technologies

The Essential Guide to Clean Code Practices for JavaScript, React, and Node.js Projects

Explore essential clean code practices for JavaScript, React, and Node.js. Learn best practices, tools, and techniques to create maintainable and efficient code.
calendar 02 Jul 2024
[Design Pattern] Lesson 06: Prototype Design Pattern in Java
Technologies

[Design Pattern] Lesson 06: Prototype Design Pattern in Java

As one of the popular patterns in the Creational group, the Prototype pattern is highly effective for creating complex objects. Design a Prototype pattern in Java.
calendar 04 Sep 2024

Want to stay updated on industry trends for your project?

We're here to support you. Reach out to us now.
Contact Message Box
Back2Top