You are currently viewing Java thread-safe singleton pattern (with and without lazy loading)

Java thread-safe singleton pattern (with and without lazy loading)

In the first part of the guide you get to know the right solution to implement thread safe singleton in Java. Then I’m going to explain to you what’s wrong with the most common singleton implementation and why it is not thread safe.

Resources

GitLab: java-singletons

Java thread safe singleton implementation without lazy loading

The easiest and thread safe singleton implementation in Java is to use enum class. It might be not intuitive but it works perfect if you don’t need lazy loading. Enum instances are guaranteed to be created exactly once that you don’t have to worry about thread safe initialization. So let’s see how the implementation looks like in the following example:

public enum DummySingleton {
    INSTANCE;

    public static DummySingleton getInstance() {
        return INSTANCE;
    }

    //This is dummy singleton's field
    private String dummyField = "Hello World!";

    //This is dummy singleton's method
    public void dummyMethod(String stringParam) {
        this.dummyField = stringParam;
    }

    //This is dummy singleton's method
    public String getDummyField() {
        return this.dummyField;
    }
}

Java thread safe singleton with lazy loading

In case, you need a thread safe singleton with lazy loading you can use the following pattern. As you can see, it consists of a double check of the singleton instance, to check if it is null. The first one is not synchronized so it’s fast and each thread can access it without additional synchronization cost. When the singleton instance returns to null then the synchronized initialization of the singleton starts to happens in order to ensure that exactly one instance of the class has been created.

public final class DummySingletonWithLazyLoading {
    private static DummySingletonWithLazyLoading instance = null;

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

    private DummySingletonWithLazyLoading() {
    }

    private static DummySingletonWithLazyLoading initializeSingletonInstance() {
        return new DummySingletonWithLazyLoading();
    }

    //This is dummy singleton's field
    private String dummyField = "Hello World!";

    //This is dummy singleton's method
    public void dummyMethod(String stringParam) {
        this.dummyField = stringParam;
    }

    //This is dummy singleton's method
    public String getDummyField() {
        return this.dummyField;
    }
}

Non thread safe single null check singleton implementation

In the last part I would like to explain to you why one of the most common singleton implementation is not threqd safe. Let’s have look at the following example. I intentionally modified the initialization time of the first singleton instance to emphasize what may happened in that scenario in multithreading environment. So let’s suppose that two threads run almost in the same time. From perspective of the first thread it run line 8 and because the instance is equal to null it run line 9. Then it have to wait some time for constructing the object (I my case it is 2000ms) then the reference of the object is assigned to instance. If the second thread runs line 8 during the first thread is waiting for the object construction the condition in the line 8 returns true, so the second thread starts to execute line 9. In that way two instances of the singleton were created instead of one.

import java.util.concurrent.atomic.AtomicInteger;

public class NonThreadSafeDummySingleton {
    public static final AtomicInteger TOTAL_INSTANCES = new AtomicInteger();
    private static NonThreadSafeDummySingleton instance;

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

    public NonThreadSafeDummySingleton() {
        final int currentInstance = TOTAL_INSTANCES.incrementAndGet();
        System.out.println("Creating NonThreadSafeDummySingleton instance number: " + currentInstance);
        if (currentInstance == 1) {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("NonThreadSafeDummySingleton instance number: " + currentInstance + " created");
    }
}
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import static org.junit.jupiter.api.Assertions.*;

class NonThreadSafeDummySingletonTest {
    @Test
    void doubleInstanceCreatedTest() {
        //given
        CompletableFuture<Void> c1 = CompletableFuture.supplyAsync(() -> {
            NonThreadSafeDummySingleton.getInstance();
            return null;
        });

        CompletableFuture<Void> c2 = CompletableFuture.supplyAsync(() -> {
            NonThreadSafeDummySingleton.getInstance();
            return null;
        });

        List<CompletableFuture> all = List.of(c1, c2);
        //when
        all.forEach(CompletableFuture::join);
        //then
        assertEquals(2, NonThreadSafeDummySingleton.TOTAL_INSTANCES.get());
    }
}

You can run the above test to ensure the wrong behaviour of the singleton implementation.

Creating NonThreadSafeDummySingleton instance number: 2
Creating NonThreadSafeDummySingleton instance number: 1
NonThreadSafeDummySingleton instance number: 2 created
NonThreadSafeDummySingleton instance number: 1 created

That’s all what I’ve prepared for you in this tutorial, if I helped you, please consider sharing this post to help me gain a wider audience.
Thanks and I hope to see you in my next tutorial.

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments