How to Implement Thread-Safe Lazy Initialization in Java

Shubham Vora Feb 02, 2024
  1. Object Initialization in Java
  2. Implement Lazy Initialization in Java
  3. Use the synchronized Method for Thread-Safe Lazy Initialization in Java
  4. Use the Double-Checked Locking Method for Thread-Safe Lazy Initialization in Java
How to Implement 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.

  1. Use the synchronize keyword before the function name.
  2. 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.

Author: Shubham Vora
Shubham Vora avatar Shubham Vora avatar

Shubham is a software developer interested in learning and writing about various technologies. He loves to help people by sharing vast knowledge about modern technologies via different platforms such as the DelftStack.com website.

LinkedIn GitHub