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
Please enter at least three characters to search
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

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Unlocking Performance: Exploring Java 21 Virtual Threads [Video]
  • Double-Checked Locking Design Pattern in Java
  • Java: How Object Reuse Can Reduce Latency and Improve Performance

Trending

  • Unlocking AI Coding Assistants Part 4: Generate Spring Boot Application
  • Unlocking the Benefits of a Private API in AWS API Gateway
  • Breaking Bottlenecks: Applying the Theory of Constraints to Software Development
  • Unlocking AI Coding Assistants Part 3: Generating Diagrams, Open API Specs, And Test Data
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Visualizing Thread-Safe Singletons in Java

Visualizing Thread-Safe Singletons in Java

In this article, I will dive deep into the singleton pattern in Java with a multi-threaded environment view.

By 
Nilotpal Sarkar user avatar
Nilotpal Sarkar
·
Updated Apr. 21, 22 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
5.3K Views

Join the DZone community and get the full member experience.

Join For Free

Working in Java, we seldom have to create our handwritten singletons, for the existing frameworks like Spring creates these objects for us and maintains them. In enterprise applications, we get into framework disposal and let them create the singletons.

But it is interesting to know the singleton pattern and visualize the famous pattern in a multi-threaded environment. In this article, I will dive deep into this pattern with a multi-threaded environment view.

I am expecting the reader to be a bit familiar with multithreading.

Breaking Down Singleton Pattern

Singleton pattern is a creational design pattern where we restrict the creation of an object to a single instance, in the complete running process. This is possibly because the object is such that multiple instances aren't necessary by functionality or design. 

Examples and Advantages

A class doing some generic mathematical computation can have a single instance across the process. Whenever a method of the class is required, we can reuse the single object created. 

This has multiple advantages:

  1. Let's say we need mathematical operation sin(radian)/cos(radian) and we use this operation 1k times in a single iteration of a business call. We certainly run through multiple such business calls, which means around 1k unnecessary objects would be created and garbage collection has to run more frequently. It's always good to make the GC run as little as possible.
  2. Sometimes singleton is desired as part of business functionality itself which we might fail to observe.  For example, let's say we design a university web application with students, their professors, and other real entities. Each different student should be created in memory (Heap) because there exist multiple students; but what about the university itself? How do you put a constraint over a university, not to have multiple instances in the heap? That would be chaos in the case by mistake 2 instances university exists in memory and both the instance carry bit different updated information of the students. To be precise, the university should not have multiple instances in a single deployment. 

Understanding Singleton Pattern in Java

That said, let's now discuss a bit how we understand the singleton pattern in Java.

In nutshell, we should not allow the class to be created publicly. We should handle the creation privately. This would allow stringent law of single creation enforced! We should keep things in control.

public class University {
    private static University university = null;

    private University(String name) {
        System.out.println("University created - "+ name);
    }

    public static University getInstance(String name) {

        if (university == null) {
            university = new University(name);
        }
        return university;
    }
}

Now, let's test it using multiple threads.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;

public class UniversityTest {

    @org.junit.Test
    public void getInstance() {

        /**
         * Lets say we have spawned 5 threads of the University app deployment.
         */

        Function<String, University> function = (name) -> University.getInstance(name);

        Runnable runnable1 = () -> function.apply("Birla Institute of technology");
        Runnable runnable2 = () -> function.apply("Birla Institute of technology");
        Runnable runnable3 = () -> function.apply("Birla Institute of technology");
        Runnable runnable4 = () -> function.apply("Birla Institute of technology");
        Runnable runnable5 = () -> function.apply("Birla Institute of technology");

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        long startTime = System.nanoTime();
        executorService.execute(runnable1);
        executorService.execute(runnable2);
        executorService.execute(runnable3);
        executorService.execute(runnable4);
        executorService.execute(runnable5);


        long endTime = System.nanoTime();
        long completionTime = endTime - startTime;
        System.out.println("Completion time : "+completionTime + " nanoseconds("+(float)completionTime/1000000+"ms)");
    }
}

Output : <Most of the time>

HTTP
 
University created - Birla Institute of Technology

University created - Birla Institute of Technology

University created - Birla Institute of Technology

University created - Birla Institute of Technology

University created - Birla Institute of Technology

Completion time : 1103152 nanoseconds(1.103152ms)

We see that the code misbehaved in a multi-threaded env. University got created more than once in most of the runs. I am expecting that the reason is known that the creation is handled in an unsynchronised manner. Multiple threads hold the creation in its own stack.

This can be resolved if we synchronized the getInstance() method itself. Let's do it.

public synchronized static University getInstance1(String name) {
    if (university == null) {
        university = new University(name);
    }
    return university;
}

Output:

 
University created - Birla Institute of technology

Completion time : 1541073 nanoseconds(1.541073ms)

Here, we got University created just once, which was triggered using the same test method used above. We would be using the same test method once and again to test it. Synchronizing solves our issue. However, synchronizing the whole method is often not a good idea. This is because other threads have to wait for the completion of the entire method completion to get hold of it. We should rather synchronize only the part of the code required to be exclusive. 

That can be done using the lock variable. Let's see how.

We can use a lock object to lock the access to the block of code only with lock object monitor.

public class University {
    private static  University university = null;
    private static Object lock = new Object();

    private University(String name) {
        System.out.println("University created - "+ name);
    }

    public static University getInstance(String name) {
        if(university == null) {
            synchronized (University.class) {
                    university = new University(name);
            }
        }
        return university;
    }
}

Another option is that we can lock the entire class access, using synchronized (University.class). This isn't a good way to lock class because it locks the entire class from access, meaning no other methods can be accessed by any thread. In our case, it doesn't makes difference, as we don't have another method to access.

public static University getInstance(String name) {
    if(university == null) {
        synchronized (University.class) {
                university = new University(name);
        }
    }
    return university;
}

Result:

 
University created - Birla Institute of technology

University created - Birla Institute of technology

University created - Birla Institute of technology

University created - Birla Institute of technology

University created - Birla Institute of technology

Completion time : 1213831 nanoseconds(1.213831ms)

But this is not what we wanted!

We synchronized the block using either lock monitor object or University.class and got no effect. The university object got created multiple times (more than once, mostly).

What went wrong here?

Let's understand this with the help of a diagram below. The explanation is also given within the diagram.

Double-checking is required in synchronized block object creation in Singleton pattern.

public static University getInstance(String name) {
    if(university == null) {
        synchronized (lock) {
            if( university == null)
                university = new University(name);
        }
    }
    return university;
}

Output:

 
University created - Birla Institute of technology

Completion time : 855721 nanoseconds(0.855721ms)

Yes! We achieved the university object creation just once in a multithreaded environment with performance in mind!

Threading Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Mastering Thread-Local Variables in Java: Explanation and Issues
  • Unlocking Performance: Exploring Java 21 Virtual Threads [Video]
  • Double-Checked Locking Design Pattern in Java
  • Java: How Object Reuse Can Reduce Latency and Improve Performance

Partner Resources

×

Comments
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!