The `synchronized` keyword is used in the Singleton Design Pattern to ensure thread safety in multithreaded environments. Here's when and why you should use it:
Why Use `synchronized` in Singleton?**
-
Thread Safety:
- In a multithreaded environment, multiple threads might simultaneously attempt to create a Singleton instance. Without synchronization, this can result in multiple instances being created, violating the Singleton's intent.
- The
synchronized
keyword ensures that only one thread at a time executes the critical section of code responsible for creating the Singleton instance.
- Data Consistency:
- It prevents threads from accessing or modifying the Singleton's shared instance in an inconsistent state.
When to Use `synchronized`?**
-
Eager Initialization:
- No need to use
synchronized
if the Singleton instance is created at the time of class loading (eager initialization). This approach inherently avoids multithreading issues since the instance is created before any thread accesses it.
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
-
Lazy Initialization:
- Use
synchronized
when the Singleton instance is initialized lazily (i.e., when first accessed). Lazy initialization is prone to race conditions, and synchronization ensures only one instance is created.
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
-
Double-Checked Locking for Better Performance:
- If you want lazy initialization with improved performance, you can use double-checked locking. This reduces the overhead of synchronization by ensuring it only occurs the first time the instance is created.
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
When Not to Use `synchronized`?**
-
Single-Threaded Environment:
- Synchronization is unnecessary in single-threaded applications because thread safety is not a concern.
-
Eager Initialization:
- Since the instance is created during class loading, synchronization is redundant.
Key Considerations**
-
Performance:
- The
synchronized
keyword can introduce performance overhead due to locking. Use it judiciously, and prefer double-checked locking or eager initialization if performance is critical.
-
Volatile Keyword:
- When using double-checked locking, declare the
instance
variable as volatile
to prevent issues with instruction reordering.
By understanding your application's requirements and environment, you can decide when to use `synchronized` with the Singleton pattern effectively.
Given :
11.class ChocolateBoiler {
12. private static ChocolateBoiler boiler;
13. private ChocolateBoiler() { }
14. public static final ChocolateBoiler getInstance() {
15. if(boiler == null) {
16. boiler = new ChocolateBoiler();
17. }
18. return boiler;
19.}
The above code shows a class depicting the
Singleton design pattern. What can be done to make it a
perfect Singleton design pattern?
Please select 1 option:
- Remove the if statement on line 15
- The given code is already a perfect singleton design pattern.
- Mark the static boiler instance variable as final.
- Synchronize the getInstance method using synchronized keyword.
In this code fragment, there is a
class
ChocolateBoiler which has a
- static final method getInstance and
- a static instance variable boiler.
The default constructor of the ChocolateBoiler class is marked as private.
The constructor of this class cannot be called from an outer class because it is private. This prevents the instantiation of the class from an outer class.
Hence, only a static method inside the class can access the constructor of the class. The getInstance method serves this purpose and when the getInstance method is invoked, it checks for the boiler variable to be null.
If yes, it instantiates the boiler variable, else it returns the object which is already referenced by the boiler variable.
Hence, it is verified that there is
one and only one object that is created and returned when asked for.
This type of design pattern is called the Singleton design pattern in Java. However, when
multithreading comes into the picture, if two or more threads somehow enter the "if block" in the getInstance method, there will be two instances of the ChocolateBoiler which is contradictory to the Singleton pattern.
So marking the getInstance method as
synchronized ensures thread safety because only one thread will be able to enter the method at a time.
//According to the Oracle docs, the synchronized keyword should appear after the public keyword
// https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
class PerfectChocolateBoiler {
private static PerfectChocolateBoiler boiler;
private PerfectChocolateBoiler() { }
public synchronized static final
PerfectChocolateBoiler getInstance() {
if (boiler == null) {
boiler = new PerfectChocolateBoiler();
}
return boiler;
}
public static void main(String[] args) {
// more code here
}
}