遅延初期化スレッドセキュリティ実装
13214 ワード
1.遅延初期化の不適切な実現非スレッドセキュリティバージョン 単一のモードでは、遅延初期化(lazy initialization)が使用されることが多い.すなわち、最初の使用時にオブジェクトを初期化し、オーバーヘッドの高いオブジェクトの初期化作業を遅らせる.次は非スレッドセキュリティの遅延初期化コードです.
このコードの問題は、マルチスレッドの場合、次のアクセス順序が発生する可能性があることです.
Time
Thread A
Thread B
T1
uniqueSingletonが空であることを確認
T2
uniqueSingletonが空であることを確認
T3
イニシャルオブジェクトA
T4
オブジェクトAに戻る
T5
イニシャルオブジェクトB
T6
オブジェクトBに戻る
この2つのスレッドは,このクラスの2つの異なるインスタンスを有し,単一例モードの初心に反している.同期ロックバージョン
しかし、エラーのデュアルチェックロックバージョン では、最適化を検討して、二重チェックロックを使用することができます.ロックをかける前に一度チェックして、オブジェクトの初期化が完了したら、ロックを追加する必要はありません.コードは以下の通りです.
これにより、複数のオブジェクトとパフォーマンスのオーバーヘッドが発生する問題が完全に解決されたようです.複数のスレッドが1回目のチェックを通過すると、1つのスレッドだけが最初に同期ロックを取得し、2回目のチェックを通過してオブジェクトを作成することができ、その後、1回目のチェックを通過した後、同期ロックを取得するスレッドは、2回目のチェックが通過しないため、オブジェクトを作成する必要はありません.1回目のチェックに合格していないスレッドは、1回目のチェックが合格していないため、オブジェクトを作成する必要はありません. は、すべてのスレッドを同期する必要はありません.スレッドがオブジェクトを作成した後、他のすべてのスレッドが任意にチェックでき、オブジェクトが初期化され、返されていることがわかります.
実はそうではない.
2.スレッドの安全な遅延初期化——volatileに基づく二重チェックロック実現
上の二重チェックロックのコードは実際にはエラーであり、問題の根源は、初期化オブジェクトが原子操作ではなく、再ソートが発生する可能性があることです.初期化オブジェクトのコード、すなわち上のコードにerrorがマークされている行は、i.メモリ空間の割り当てii.初期化オブジェクトiiii.割り当てられたばかりのメモリ空間にオブジェクトを向ける3つのステップに分解することができ、いくつかのJITコンパイラでは、パフォーマンスの理由で第2ステップと第3ステップが並べ替えられる可能性がある.2つのスレッドの実行順序は次のとおりです.
Time
Thread A
Thread B
T1
uniqueSingletonが空であることを確認
T2
ロックの取得
T3
uniqueSingletonが空であることを再確認
T4
uniqueSingletonにメモリ領域を割り当てる
T5
uniqueSingletonをメモリ領域に向ける
T6
uniqueSingletonが空でないことを確認
T7
uniqueSingleton(初期化されていないオブジェクト)へのアクセス
T8
uniqueSingletonの初期化
このとき、スレッドBは、初期化されていないオブジェクト(ランダム値を有するメモリ)を読み出す.この問題を解決するためには,
3.スレッドの安全な遅延初期化——クラス初期化ロックに基づく実現
JVMはクラスの初期化フェーズ(つまりクラスがロードされ、スレッドが使用される前)でクラスの初期化を実行します.クラスの初期化を実行する間、JVMはロックを取得します.このロックは、複数のスレッドによる同じクラスの初期化を同期できます.この特性に基づいて、コードは以下のように、別のスレッドセキュリティの遅延初期化スキーム(initialization-on-demand holder idiomと呼ばれる)を実現することができる.
このスキームはJavaにおけるクラスの初期化ロックLCを利用している.JLS(Java SE 8 Edition)の「§12.4.1 When Initialization Occurs」セクションの説明に従います.
A class or interface type T will be initialized immediately before the first occurrence of any one of the following: T is a class and an instance of T is created. A static method declared by T is invoked. A static field declared by T is assigned. A static field declared by T is used and the field is not a constant variable (§4.12.4). T is a top level class (§7.6) and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.
上記のコードでは
4.参考文献:
[1]二重チェックロックと遅延初期化http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization#anch136785 [2]Javaでのダブルチェックロック(double checked locking)https://www.cnblogs.com/xz816111/p/8470048.html [3] Double-checked locking and the Singleton pattern https://www.ibm.com/developerworks/java/library/j-dcl/index.html [4] Double-checked locking https://en.wikipedia.org/wiki/Double-checked_locking [5] The Java Language Specification, Java SE 8 Edition https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4
public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
このコードの問題は、マルチスレッドの場合、次のアクセス順序が発生する可能性があることです.
Time
Thread A
Thread B
T1
uniqueSingletonが空であることを確認
T2
uniqueSingletonが空であることを確認
T3
イニシャルオブジェクトA
T4
オブジェクトAに戻る
T5
イニシャルオブジェクトB
T6
オブジェクトBに戻る
この2つのスレッドは,このクラスの2つの異なるインスタンスを有し,単一例モードの初心に反している.
synchronized
キーワードで方法を修飾し、同期ロックを加えることができます.public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
return uniqueSingleton;
}
}
しかし、
synchronized
はパフォーマンスオーバーヘッドを引き起こし、実際には最初の初期化時にのみロックされ、後で呼び出される場合はロックする必要はありません.public class Singleton {
private static Singleton uniqueSingleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton(); // error
}
}
}
return uniqueSingleton;
}
}
これにより、複数のオブジェクトとパフォーマンスのオーバーヘッドが発生する問題が完全に解決されたようです.
実はそうではない.
2.スレッドの安全な遅延初期化——volatileに基づく二重チェックロック実現
上の二重チェックロックのコードは実際にはエラーであり、問題の根源は、初期化オブジェクトが原子操作ではなく、再ソートが発生する可能性があることです.初期化オブジェクトのコード、すなわち上のコードにerrorがマークされている行は、i.メモリ空間の割り当てii.初期化オブジェクトiiii.割り当てられたばかりのメモリ空間にオブジェクトを向ける3つのステップに分解することができ、いくつかのJITコンパイラでは、パフォーマンスの理由で第2ステップと第3ステップが並べ替えられる可能性がある.2つのスレッドの実行順序は次のとおりです.
Time
Thread A
Thread B
T1
uniqueSingletonが空であることを確認
T2
ロックの取得
T3
uniqueSingletonが空であることを再確認
T4
uniqueSingletonにメモリ領域を割り当てる
T5
uniqueSingletonをメモリ領域に向ける
T6
uniqueSingletonが空でないことを確認
T7
uniqueSingleton(初期化されていないオブジェクト)へのアクセス
T8
uniqueSingletonの初期化
このとき、スレッドBは、初期化されていないオブジェクト(ランダム値を有するメモリ)を読み出す.この問題を解決するためには,
volatile
キーワードで変数uniqueSingleton
を修飾するだけでよいが,正しい二重チェックロックの実装コードは以下の通りである.public class Singleton {
private volatile static Singleton uniqueSingleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == uniqueSingleton) {
synchronized (Singleton.class) {
if (null == uniqueSingleton) {
uniqueSingleton = new Singleton();
}
}
}
return uniqueSingleton;
}
}
volatile
を使用すると、並べ替えが禁止され、すべての書き込み操作(write)が読み取り操作(read)の前に発生します.3.スレッドの安全な遅延初期化——クラス初期化ロックに基づく実現
JVMはクラスの初期化フェーズ(つまりクラスがロードされ、スレッドが使用される前)でクラスの初期化を実行します.クラスの初期化を実行する間、JVMはロックを取得します.このロックは、複数のスレッドによる同じクラスの初期化を同期できます.この特性に基づいて、コードは以下のように、別のスレッドセキュリティの遅延初期化スキーム(initialization-on-demand holder idiomと呼ばれる)を実現することができる.
public class Singleton {
private static class SingletonHolder {
public static Singleton uniqueSingleton = new Singleton();
}
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.uniqueSingleton;
}
}
このスキームはJavaにおけるクラスの初期化ロックLCを利用している.JLS(Java SE 8 Edition)の「§12.4.1 When Initialization Occurs」セクションの説明に従います.
A class or interface type T will be initialized immediately before the first occurrence of any one of the following:
上記のコードでは
getInstance()
メソッドが初めて呼び出されると、上記の4つ目のケースに該当し、クラスSingletonHolder
が初期化され、LCを用いて同期される.これにより,JVMによるクラス初期化ロックが利用され,遅延初期化されたスレッドセキュリティバージョンが実現される.LCの詳細については、参考文献[5]の「§12.4.2.Detailed Initialization Procedure」の項を参照してください.4.参考文献:
[1]二重チェックロックと遅延初期化http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization#anch136785 [2]Javaでのダブルチェックロック(double checked locking)https://www.cnblogs.com/xz816111/p/8470048.html [3] Double-checked locking and the Singleton pattern https://www.ibm.com/developerworks/java/library/j-dcl/index.html [4] Double-checked locking https://en.wikipedia.org/wiki/Double-checked_locking [5] The Java Language Specification, Java SE 8 Edition https://docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.4