How to Implement Thread-Safe Lazy Initialization in Java
- Object Initialization in Java
- Implement Lazy Initialization in Java
-
Use the
synchronized
Method for Thread-Safe Lazy Initialization in Java - Use the Double-Checked Locking Method for Thread-Safe Lazy Initialization in Java
This article will discuss implementing thread-safe lazy initialization in Java.
Object Initialization in Java
Lazy initialization is the act of delaying object creation. It can also cause delays in some calculation tasks or expensive processes for the first time.
There are two types of object initialization in Java. These are the Eager initialization and the Lazy initialization.
The object initialization occurs during the compile time in the Eager initialization. In the case of a lengthy process, this is time and memory-consuming.
In Lazy initialization, the object initialization happens when the program needs it. So it saves memory, increases processing power, and improves efficiency.
Threads are minor components that can run processes in parallel to optimize time. Thread Safe
is a Java class that ensures the correctness of the class’s internal state and values that the functions return during multiple thread calls simultaneously.
Implement Lazy Initialization in Java
A getter method checks whether a private member has some value already. If it has, the function returns it; else, it creates a new instance and returns it for the first time execution.
There are two methods to do lazy initialization.
- Use the
synchronize
keyword before the function name. - Use the double-checked locking or the synchronized block Method.
Write Thread Safe Code for Lazy Initialization in Java
A variable may return unexpected results when multiple threads run in parallel. Here, we need a thread-safe
code to prevent deadlocks in Java.
Example:
Consider a bank where only one token counter is available for token distribution. The task here is to create a Bank
class only when a customer comes in; the process is lazy initialization.
Let us create multiple threads, say t1
, t2
, and so on, to understand multiple thread calls. Each thread will be a customer.
When a customer comes for the token, we create a Bank
object and call operation()
with the token number in the function argument. Now, display whether a token is available and specify the next action in the message.
If the token is available, issue the token; else, proceed to the next customer.
The program explains both the getInstanceSynchronizedWay()
and the getInstanceSynchronizedBlockWay()
. We can use Thread.sleep
to ensure output accuracy.
Use the synchronized
Method for Thread-Safe Lazy Initialization in Java
In this example, we have two customer threads.
The _instance
variable checks whether an instance is null. If the variable is null, the program creates an instance.
The empty flag checks whether the token is available or not. If the token is available, the flag value is true.
After giving the token to the customer, the program sets the flag value to false. So, the next customer will not get the token until the operation()
completes.
Example Code:
// Helper class as a Singleton Class
class BankOperation {
// Private variables
private static BankOperation _instance;
private boolean empty = false;
private String customerName = "default";
// Displays the instance only during creation
private BankOperation() {
System.out.println("Instance Creation Over\n");
}
// synchronized method
public static synchronized BankOperation getInstanceSynchronizedWay() {
if (_instance == null)
_instance = new BankOperation();
return _instance;
}
// Check if the token is available
public boolean isOperationBankEmpty() {
return empty;
}
// When token giving is successful
public void endOperation() {
empty = true;
}
// Multiple threads access the method below
public synchronized void operation(String paramCust) {
// When the token is available. The flag is true.
if (empty == true) {
customerName = paramCust;
// Issue the token to the customer
System.out.println("Operation - Token is available.\n"
+ "Giving token to - " + customerName);
empty = false;
}
// The token is not available
else {
System.out.println("Sorry " + paramCust + ", Counter closed with " + customerName);
}
}
}
// Main class
public class Bank {
// Driver function
public static void main(String args[]) {
// synchronized method
// Create a thread in main()
Thread t1 = new Thread(new Runnable() {
// run() for thread 1
public void run() {
// Create objects of other classes here
BankOperation i1 = BankOperation.getInstanceSynchronizedWay();
System.out.println("Synchronized Method - Instance 1 - " + i1 + "\n");
// The method with an argument
i1.endOperation();
i1.operation("Customer 1");
}
});
// Thread 2
Thread t2 = new Thread(new Runnable() {
// run() for thread 2
public void run() {
BankOperation i2 = BankOperation.getInstanceSynchronizedWay();
System.out.println("Synchronized Method - Instance 2 - " + i2 + "\n");
i2.operation("Customer 2");
}
});
// Starting thread 1
t1.start();
// Start thread 2
t2.start();
}
}
Output:
Instance Creation Over
Synchronized Method - Instance 1 - BankOperation@792bbbb1
Synchronized Method - Instance 2 - BankOperation@792bbbb1
Operation - Token is available.
Giving the token to - Customer 1
Sorry Customer 2, Counter closed with Customer 1
In the above output, users can observe that two customer instances come in parallel. So, the first instance gets the token, and the second instance gets a message.
Use the Double-Checked Locking Method for Thread-Safe Lazy Initialization in Java
In this example, we create two customer threads. The _instanceForDoubleCheckLocking
variable checks twice whether an instance is null, and the method getInstanceSynchronizedBlockWay()
returns the new instance.
The value of the flag empty
determines the token availability. The token is available to the customer if the flag is true.
The flag becomes false after the customer gets the token. Running multiple threads cannot change any value until the current thread finishes its operation.
Example Code:
// Helper class
class BankOperation {
// Private variable declaration
private static BankOperation _instanceForDoubleCheckLocking;
private boolean empty = false;
private String customerName = "default";
private BankOperation() {
System.out.println("Instance Creation Over\n");
}
// Synchronized Block Method or Double-Checked Locking
public static BankOperation getInstanceSynchronizedBlockWay() {
// Check double locking
if (_instanceForDoubleCheckLocking == null)
synchronized (BankOperation.class) {
if (_instanceForDoubleCheckLocking == null)
_instanceForDoubleCheckLocking = new BankOperation();
}
return _instanceForDoubleCheckLocking;
}
// The `token` availability check
public boolean isOperationBankEmpty() {
return empty;
}
// After giving the token set the flag value
public void endOperation() {
empty = true;
}
// Multiple threads access the method below
public synchronized void operation(String paramCust) {
// The flag is true when the `token` is available
if (empty == true) {
customerName = paramCust;
// Give the `token` to the customer
System.out.println("Operation - Token is available.\n"
+ "Giving token to - " + customerName);
empty = false;
}
// When the `Token` is not available
else {
System.out.println("Sorry " + paramCust + ", Counter closed with " + customerName);
}
}
}
// Main class
public class Bank {
// Driver function
public static void main(String args[]) {
// Double Checked Locking
System.out.println("Double Checked locking - Synchronized Block");
// Thread 3
Thread t3 = new Thread(new Runnable() {
// run() for thread 3
public void run() {
BankOperation i1 = BankOperation.getInstanceSynchronizedBlockWay();
System.out.println("Double Checked Locking - Instance 1 - " + i1 + "\n");
i1.endOperation();
i1.operation("Customer 1");
}
});
// Thread 4
Thread t4 = new Thread(new Runnable() {
// run() for thread 4
public void run() {
BankOperation i2 = BankOperation.getInstanceSynchronizedBlockWay();
System.out.println("Double Checked Locking - Instance 2 - " + i2 + "\n");
i2.operation("Customer 2");
}
});
t3.start();
t4.start();
}
}
Output 1:
Double Checked locking - Synchronized Block
Instance Creation Over
Double Checked Locking - Instance 1 - BankOperation@1efc89d6
Double Checked Locking - Instance 2 - BankOperation@1efc89d6
Operation - Token is available.
Giving token to - Customer 1
Sorry Customer 2, Counter closed with Customer 1
Note that when two customer instances arrive in parallel, the program supplies the token to the first instance and notifies the second customer.
Output 2:
Double Checked locking - Synchronized Block
Instance Creation Over
Double Checked Locking - Instance 2 - BankOperation@282416b6
Double Checked Locking - Instance 1 - BankOperation@282416b6
Sorry Customer 2, the counter closed with the default
Operation - Token is available.
Giving Bank token to - Customer 1
Here, the token is unavailable to customer 2, and the reason is that the endOperation()
is missing before the operation()
. Customer 1 gets the token because the endOperation()
runs before the operation()
.
This tutorial taught us two methods to implement thread-safe lazy initialization in Java. The first method uses the synchronized
keyword, and the second option is the synchronized block method.
The synchronized block method ensures a double-check. Users can choose any one way based on the context.