DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Redefining Java Object Equality
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech
  • Exploring Exciting New Features in Java 17 With Examples
  • All You Need To Know About Garbage Collection in Java

Trending

  • How Large Tech Companies Architect Resilient Systems for Millions of Users
  • The Modern Data Stack Is Overrated — Here’s What Works
  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  • Java’s Next Act: Native Speed for a Cloud-Native World
  1. DZone
  2. Coding
  3. Java
  4. Singleton: 6 Ways To Write and Use in Java Programming

Singleton: 6 Ways To Write and Use in Java Programming

Here, learn how and when to write singletons and gain an appreciation of the nuances and caveats in creating singleton objects.

By 
Narendran Solai Sridharan user avatar
Narendran Solai Sridharan
·
Apr. 09, 24 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
6.5K Views

Join the DZone community and get the full member experience.

Join For Free

In Java programming, object creation or instantiation of a class is done with "new" operator and with a public constructor declared in the class as below.

Java
 
Clazz clazz = new Clazz();


We can read the code snippet as follows:

Clazz() is the default public constructor called with "new" operator to create or instantiate an object for Clazz class and assigned to variable clazz, whose type is Clazz.

While creating a singleton, we have to ensure only one single object is created or only one instantiation of a class takes place. To ensure this, the following common things become the prerequisite.

  1. All constructors need to be declared as "private" constructors. 
    • It prevents the creation of objects with "new" operator outside the class.
  2. A private constant/variable object holder to hold the singleton object is needed; i.e., a private static or a private static final class variable needs to be declared.
    • It holds the singleton object. It acts as a single source of reference for the singleton object
    • By convention, the variable is named as INSTANCE or instance.
  3. A static method to allow access to the singleton object by other objects is required.
    • This static method is also called a static factory method, as it controls the creation of objects for the class.
    • By convention, the method is named as getInstance(). 

With this understanding, let us delve deeper into understanding singleton. Following are the 6 ways one can create a singleton object for a class.

1. Static Eager Singleton Class

When we have all the instance properties in hand, and we like to have only one object and a class to provide a structure and behavior for a group of properties related to each other, we can use the static eager singleton class. This is well-suited for application configuration and application properties.

Java
 
public class EagerSingleton {
  
  private static final EagerSingleton INSTANCE = new EagerSingleton();

  private EagerSingleton() {}
      
  public static EagerSingleton getInstance() {
    return INSTANCE;
  }

  public static void main(String[] args) {
    EagerSingleton eagerSingleton = EagerSingleton.getInstance();
  }
}


The singleton object is created while loading the class itself in JVM and assigned to the INSTANCE constant. getInstance() provides access to this constant.

While compile-time dependencies over properties are good, sometimes run-time dependencies are required. In such a case, we can make use of a static block to instantiate singleton.

Java
 
public class EagerSingleton {

    private static EagerSingleton instance;

    private EagerSingleton(){}

 // static block executed during Class loading
    static {
        try {
            instance = new EagerSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occurred in creating EagerSingleton instance");
        }
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}


The singleton object is created while loading the class itself in JVM as all static blocks are executed while loading. Access to the instance variable is provided by the getInstance() static method.

2. Dynamic Lazy Singleton Class

Singleton is more suited for application configuration and application properties. Consider heterogenous container creation, object pool creation, layer creation, facade creation, flyweight object creation, context preparation per requests, and sessions, etc.: they all require dynamic construction of a singleton object for better "separation of concern." In such cases, dynamic lazy singletons are required.

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

 

The singleton object is created only when the getInstance() method is called. Unlike the static eager singleton class, this class is not thread-safe.

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

}


The getInstance() method needs to be synchronized to ensure the getInstance() method is thread-safe in singleton object instantiation.

3. Dynamic Lazy Improved Singleton Class

Java
 
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance() {
      if (instance == null) {
        synchronized (LazySingleton.class) {
            if (instance == null) {
                instance = new LazySingleton();
            }
        }
      }
      return instance;
    }

}


Instead of locking the entire getInstance() method, we could lock only the block with double-checking or double-checked locking to improve performance and thread contention.

Java
 
public class EagerAndLazySingleton {

    private EagerAndLazySingleton(){}

    private static class SingletonHelper {
        private static final EagerAndLazySingleton INSTANCE = new EagerAndLazySingleton();
    }

    public static EagerAndLazySingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}


The singleton object is created only when the getInstance() method is called. It is a Java memory-safe singleton class. It is a thread-safe singleton and is lazily loaded. It is the most widely used and recommended.

Despite performance and safety improvement, the only objective to create just one object for a class is challenged by memory reference, reflection, and serialization in Java.

  • Memory reference: In a multithreaded environment, reordering of read and writes for threads can occur on a referenced variable, and a dirty object read can happen anytime if the variable is not declared volatile.
  • Reflection: With reflection, the private constructor can be made public and a new instance can be created. 
  • Serialization: A serialized instance object can be used to create another instance of the same class. 

All of these affect both static and dynamic singletons. In order to overcome such challenges, it requires us to declare the instance holder as volatile and override equals(), hashCode() and readResolve() of default parent class of all classes in Java, Object.class.

4. Singleton With Enum

The issue with memory safety, reflection, and serialization can be avoided if enums are used for static eager singleton. 

Java
 
public enum EnumSingleton {
    INSTANCE;
}


These are static eager singletons in disguise, thread safe. It is good to prefer an enum where a static eagerly initialized singleton is required.

5. Singleton With Function and Libraries

While understanding the challenges and caveats in singleton is a must to appreciate, why should one worry about reflection, serialization, thread safety, and memory safety when one can leverage proven libraries? Guava is such a popular and proven library, handling a lot of best practices for writing effective Java programs.

I have had the privilege of using the Guava library to explain supplier-based singleton object instantiation to avoid a lot of heavy-lifting lines of code. Passing a function as an argument is the key feature of functional programming. While the supplier function provides a way to instantiate object producers, in our case, the producer must produce only one object and should keep returning the same object repeatedly after a single instantiation. We can memoize/cache the created object. Functions defined with lambdas are usually lazily invoked to instantiate objects and the memoization technique helps in lazily invoked dynamic singleton object creation.

Java
 
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;

public class SupplierSingleton {
    private SupplierSingleton() {}

    private static final Supplier<SupplierSingleton> singletonSupplier = Suppliers.memoize(()-> new SupplierSingleton());

    public static SupplierSingleton getInstance() {
        return singletonSupplier.get();
    }

    public static void main(String[] args) {
        SupplierSingleton supplierSingleton = SupplierSingleton.getInstance();
    }
}


Functional programming, supplier function, and memoization help in the preparation of singletons with a cache mechanism. This is most useful when we don't want heavy framework deployment.

6. Singleton With Framework: Spring, Guice

Why worry about even preparing an object via supplier and maintaining cache? Frameworks like Spring and Guice work on POJO objects to provide and maintain singleton.

This is heavily used in enterprise development where many modules each require their own context with many layers. Each context and each layer are good candidates for singleton patterns.

Java
 
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

class SingletonBean { }

@Configuration
public class SingletonBeanConfig {

    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
    public SingletonBean singletonBean() {
        return new SingletonBean();
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SingletonBean.class);
        SingletonBean singletonBean = applicationContext.getBean(SingletonBean.class);
    }
}


Spring is a very popular framework. Context and Dependency Injection are the core of Spring.

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

interface ISingletonBean {}

class SingletonBean implements  ISingletonBean { }

public class SingletonBeanConfig extends AbstractModule {

    @Override
    protected void configure() {
        bind(ISingletonBean.class).to(SingletonBean.class);
    }

    public static void main(String[] args) {
        Injector injector = Guice.createInjector(new SingletonBeanConfig());
        SingletonBean singletonBean = injector.getInstance(SingletonBean.class);
    }
}


Guice from Google is also a framework to prepare singleton objects and an alternative to Spring.

Following are the ways singleton objects are leveraged with "Factory of Singletons."

  • Factory Method, Abstract Factory, and Builders are associated with the creation and construction of specific objects in JVM. Wherever we envision the construction of an object with specific needs, we can discover the singleton's need. Further places where one can check out and discover singleton are as follows.
  1. Prototype or Flyweight
  2. Object pools
  3. Facades
  4. Layering
  5. Context and class loaders
  6. Cache
  7. Cross-cutting concerns and aspect-oriented programming

Conclusion

Patterns appear when we solve use cases for our business problems and for our non-functional requirement constraints like performance, security, and CPU and memory constraints. Singleton objects for a given class is such a pattern, and requirements for its use will fall in place to discover. The class by nature is a blueprint to create multiple objects, yet the need for dynamic heterogenous containers to prepare "context," "layer,", "object pools," and "strategic functional objects" did push us to make use of declaring globally accessible or contextually accessible objects. 

Thanks for your valuable time, and I hope you found something useful to revisit and discover.

Dependency injection Functional programming Google Guava Instance variable Java virtual machine Thread safety Java (programming language) Memory (storage engine) Object (computer science) Data Types

Opinions expressed by DZone contributors are their own.

Related

  • Redefining Java Object Equality
  • Java vs. Scala: Comparative Analysis for Backend Development in Fintech
  • Exploring Exciting New Features in Java 17 With Examples
  • All You Need To Know About Garbage Collection in Java

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: