Java でのスレッドセーフな遅延初期化
- Java でのオブジェクトの初期化
- Java で遅延初期化を実装する
-
Java でスレッドセーフな遅延初期化に
synchronized
メソッドを使用する - Java でスレッドセーフな遅延初期化にダブルチェック ロック方式を使用する
この記事では、Java でのスレッドセーフな遅延初期化の実装について説明します。
Java でのオブジェクトの初期化
遅延初期化は、オブジェクトの作成を遅らせる行為です。 また、一部の計算タスクや最初の高価なプロセスで遅延が発生する可能性もあります。
Java には 2 種類のオブジェクトの初期化があります。 これらは Eager 初期化と Lazy 初期化です。
オブジェクトの初期化は、Eager 初期化のコンパイル時に発生します。 長いプロセスの場合、これは時間とメモリを消費します。
遅延初期化では、プログラムが必要とするときにオブジェクトの初期化が行われます。 そのため、メモリが節約され、処理能力が向上し、効率が向上します。
スレッドは、時間を最適化するためにプロセスを並行して実行できるマイナー コンポーネントです。 Thread Safe
は、複数のスレッドが同時に呼び出されたときに関数が返すクラスの内部状態と値の正確性を保証する Java クラスです。
Java で遅延初期化を実装する
ゲッター メソッドは、プライベート メンバーが既に何らかの値を持っているかどうかを確認します。 存在する場合、関数はそれを返します。 それ以外の場合は、新しいインスタンスを作成し、初回実行時にそれを返します。
遅延初期化を行うには 2つの方法があります。
- 関数名の前に
synchronize
キーワードを使用します。 - ダブルチェック ロックまたは同期ブロック メソッドを使用します。
Java での遅延初期化用のスレッド セーフ コードの記述
複数のスレッドが並行して実行されると、変数が予期しない結果を返すことがあります。 ここでは、Java でのデッドロックを防ぐために スレッドセーフ
コードが必要です。
例:
トークンの配布に使用できるトークン カウンターが 1つしかない銀行を考えてみましょう。 ここでのタスクは、顧客が入ったときにのみ Bank
クラスを作成することです。 このプロセスは遅延初期化です。
複数のスレッド呼び出しを理解するために、複数のスレッド、たとえば t1
、t2
などを作成してみましょう。 各スレッドは顧客になります。
顧客がトークンを取りに来ると、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 番目のオプションは同期ブロック法です。
同期ブロック方式により、ダブルチェックが保証されます。 ユーザーは、コンテキストに基づいていずれかの方法を選択できます。