Java
Singleton Pattern
Synchronization
Thread Safety
Design Patterns

Java Singleton and Synchronization

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

Java's Singleton design pattern is a powerful construct for creating a single instance of a class to be used throughout an application. It's a common design pattern used to ensure that a particular class has only one instance and to provide a global access point to that instance. When discussing Singletons, synchronization is an important aspect, particularly in a multi-threaded environment, to maintain its uniqueness. In this article, we'll discuss Java Singleton patterns and synchronization strategies in detail.

Java Singleton Design Pattern

A Singleton class in Java restricts the instantiation of a class and ensures that one and only one instance of the class exists at any point in time. This is particularly useful when managing shared resources or configurations. There are a few common implementations of the Singleton pattern in Java:

1. Eager Initialization

Eager initialization involves creating the singleton instance at the time of class loading. This approach is simple but doesn't allow for exception handling during instance creation.

java
1public class EagerSingleton {
2    private static final EagerSingleton INSTANCE = new EagerSingleton();
3
4    private EagerSingleton() {
5        // private constructor
6    }
7
8    public static EagerSingleton getInstance() {
9        return INSTANCE;
10    }
11}

2. Lazy Initialization

Lazy initialization defers the creation of the singleton instance until it is first requested. This can be efficient if the instance is not used until needed.

java
1public class LazySingleton {
2    private static LazySingleton instance;
3
4    private LazySingleton() {
5        // private constructor
6    }
7
8    public static LazySingleton getInstance() {
9        if (instance == null) {
10            instance = new LazySingleton();
11        }
12        return instance;
13    }
14}

Note: The lazy initialization above is not thread-safe, which leads us to consider synchronization in Singleton design.

Synchronization in Singleton Pattern

In a multi-threaded environment, the Singleton pattern must be thread-safe. Without synchronization, multiple threads could create multiple instances of the Singleton class. Here are several strategies for ensuring thread safety:

1. Thread-Safe Singleton with Synchronized Method

One way to make the Singleton lazy initialization thread-safe is to synchronize the getInstance() method.

java
1public class ThreadSafeSingleton {
2    private static ThreadSafeSingleton instance;
3
4    private ThreadSafeSingleton() {
5        // private constructor
6    }
7
8    public static synchronized ThreadSafeSingleton getInstance() {
9        if (instance == null) {
10            instance = new ThreadSafeSingleton();
11        }
12        return instance;
13    }
14}

Drawback: This approach significantly reduces performance because of the additional overhead of acquiring the lock every time the method is called.

2. Double-Checked Locking

Double-checked locking reduces the performance overhead by first checking whether the instance is null before acquiring the lock.

java
1public class DoubleCheckedLockingSingleton {
2    private static volatile DoubleCheckedLockingSingleton instance;
3
4    private DoubleCheckedLockingSingleton() {
5        // private constructor
6    }
7
8    public static DoubleCheckedLockingSingleton getInstance() {
9        if (instance == null) {
10            synchronized (DoubleCheckedLockingSingleton.class) {
11                if (instance == null) {
12                    instance = new DoubleCheckedLockingSingleton();
13                }
14            }
15        }
16        return instance;
17    }
18}

In this approach, the volatile keyword ensures that multiple threads handle the unique instance variable correctly when it is initialized to the DoubleCheckedLockingSingleton instance.

3. Bill Pugh Singleton Design

This technique leverages the class loader mechanism of Java to create the Singleton class. The instance is created in a static inner class only when it is first requested.

java
1public class BillPughSingleton {
2    private BillPughSingleton() {
3        // private constructor
4    }
5
6    private static class SingletonHelper {
7        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
8    }
9
10    public static BillPughSingleton getInstance() {
11        return SingletonHelper.INSTANCE;
12    }
13}

This is considered one of the best practices for creating a Singleton in Java due to its performance benefits and simplicity.

Table Summarizing Singleton Implementation Strategies

StrategyDescriptionSynchronizationPerformance Impact
Eager InitializationInstance is created at class loading time.NoNo lazy loading, instance is created even if never used.
Lazy InitializationInstance is created only when needed.NoNot thread-safe in a multi-threaded environment.
Synchronized MethodSynchronization ensures thread safety by locking on every call.FullHigh performance impact due to locking every call to getInstance().
Double-Checked LockingReduces the use of synchronization by checking a condition before locking.PartialPerformance benefit as locking is called only during the first call. Requires Java 5+ due to volatile.
Bill Pugh's Inner ClassInner static class provides a unique and thread-safe solution by using Java's inherent class loader mechanism.YesNo performance costs after the class is loaded, as locking is not used.

Conclusion

The Singleton design pattern is essential for controlling object creation, ensuring that a class has no more than one instance. In concurrent applications, synchronization mechanisms become crucial to maintain instance uniqueness. Each Singleton implementation strategy offers different trade-offs in terms of performance and complexity. Choosing the appropriate Singleton pattern depends on the use case and application requirements.


Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Object-Oriented Design practice on Codemia

Turn requirements into classes, and defend the design, on the problems that come up in OOD rounds.

Practice OOD

All Rights Reserved.