Java でのスレッドセーフな遅延初期化

Shubham Vora 2023年10月12日
  1. Java でのオブジェクトの初期化
  2. Java で遅延初期化を実装する
  3. Java でスレッドセーフな遅延初期化に synchronized メソッドを使用する
  4. Java でスレッドセーフな遅延初期化にダブルチェック ロック方式を使用する
Java でのスレッドセーフな遅延初期化

この記事では、Java でのスレッドセーフな遅延初期化の実装について説明します。

Java でのオブジェクトの初期化

遅延初期化は、オブジェクトの作成を遅らせる行為です。 また、一部の計算タスクや最初の高価なプロセスで遅延が発生する可能性もあります。

Java には 2 種類のオブジェクトの初期化があります。 これらは Eager 初期化と Lazy 初期化です。

オブジェクトの初期化は、Eager 初期化のコンパイル時に発生します。 長いプロセスの場合、これは時間とメモリを消費します。

遅延初期化では、プログラムが必要とするときにオブジェクトの初期化が行われます。 そのため、メモリが節約され、処理能力が向上し、効率が向上します。

スレッドは、時間を最適化するためにプロセスを並行して実行できるマイナー コンポーネントです。 Thread Safe は、複数のスレッドが同時に呼び出されたときに関数が返すクラスの内部状態と値の正確性を保証する Java クラスです。

Java で遅延初期化を実装する

ゲッター メソッドは、プライベート メンバーが既に何らかの値を持っているかどうかを確認します。 存在する場合、関数はそれを返します。 それ以外の場合は、新しいインスタンスを作成し、初回実行時にそれを返します。

遅延初期化を行うには 2つの方法があります。

  1. 関数名の前に synchronize キーワードを使用します。
  2. ダブルチェック ロックまたは同期ブロック メソッドを使用します。

Java での遅延初期化用のスレッド セーフ コードの記述

複数のスレッドが並行して実行されると、変数が予期しない結果を返すことがあります。 ここでは、Java でのデッドロックを防ぐために スレッドセーフ コードが必要です。

例:

トークンの配布に使用できるトークン カウンターが 1つしかない銀行を考えてみましょう。 ここでのタスクは、顧客が入ったときにのみ Bank クラスを作成することです。 このプロセスは遅延初期化です。

複数のスレッド呼び出しを理解するために、複数のスレッド、たとえば t1t2 などを作成してみましょう。 各スレッドは顧客になります。

顧客がトークンを取りに来ると、Bank オブジェクトを作成し、関数の引数にトークン番号を指定して operation() を呼び出します。 ここで、トークンが使用可能かどうかを表示し、メッセージで次のアクションを指定します。

トークンが利用可能な場合は、トークンを発行します。 それ以外の場合は、次の顧客に進みます。

このプログラムでは、getInstanceSynchronizedWay()getInstanceSynchronizedBlockWay() の両方について説明しています。 Thread.sleep を使用して、出力の精度を確保できます。

Java でスレッドセーフな遅延初期化に synchronized メソッドを使用する

この例では、2つの顧客スレッドがあります。

_instance 変数は、インスタンスが null かどうかをチェックします。 変数が null の場合、プログラムはインスタンスを作成します。

空のフラグは、トークンが使用可能かどうかをチェックします。 トークンが使用可能な場合、フラグ値は true です。

顧客にトークンを渡した後、プログラムはフラグ値を false に設定します。 そのため、operation() が完了するまで、次の顧客はトークンを取得しません。

コード例:

// 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();
  }
}

出力:

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

上記の出力では、ユーザーは 2つの顧客インスタンスが並行して発生していることを確認できます。 したがって、最初のインスタンスがトークンを取得し、2 番目のインスタンスがメッセージを取得します。

Java でスレッドセーフな遅延初期化にダブルチェック ロック方式を使用する

この例では、2つの顧客スレッドを作成します。 _instanceForDoubleCheckLocking 変数はインスタンスが null かどうかを 2 回チェックし、メソッド getInstanceSynchronizedBlockWay() は新しいインスタンスを返します。

フラグ empty の値は、トークンの可用性を決定します。 フラグが true の場合、顧客はトークンを使用できます。

顧客がトークンを取得すると、フラグは false になります。 複数のスレッドを実行しても、現在のスレッドが操作を終了するまで値を変更することはできません。

コード例:

// 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();
  }
}

出力 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

2つの顧客インスタンスが並行して到着すると、プログラムは最初のインスタンスにトークンを提供し、2 番目の顧客に通知することに注意してください。

出力 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

ここで、顧客 2 はトークンを使用できません。その理由は、endOperation()operation() の前にないためです。 endOperation()operation() の前に実行されるため、顧客 1 はトークンを取得します。

このチュートリアルでは、Java でスレッドセーフな遅延初期化を実装する 2つの方法を学びました。 最初の方法は synchronized キーワードを使用し、2 番目のオプションは同期ブロック法です。

同期ブロック方式により、ダブルチェックが保証されます。 ユーザーは、コンテキストに基づいていずれかの方法を選択できます。

著者: 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