Javaでは、時にはあなたはオーバーヘッド遅延初期化を削減するクラスを初期化し、オブジェクトを作成する必要があります。みんなは、ダブルチェックロックによって引き起こされる一般的な初期化遅延技術ですが、それは間違った使い方です。
たとえば、次の使用:
public class UnsafeLazyInitialization{
private static Instance instance;
public static Instance getInstance(){
if(instance == null){
instance = new Instance();
}
return instance;
}
}
実行のgetInstance()オブジェクトの初期化が完了していないスレッドと仮定し、スレッドBが決定されるインスタンスの初期化は、インスタンス再度実行され、変数がヌルです。そして、最後の二つのインスタンス変数が表示されます。
もちろん、我々はそれと単に失礼なことができ、同期、ロックを取得することができ、同時に一つだけのスレッドを保証します。
public class SafeLazyInitialization{
private static Instance instance;
public synchronized static Instance getInstance(){
if(instance == null){
instance = new Instance();
}
return instance;
}
}
起因するのgetInstance()、同期処理を行うための方法、synchrnoizedにつながるのgetInstance()メソッドは、頻繁に複数のスレッドを起動され、プログラムの実行性能低下につながります。
早期では、同期同期のオーバーヘッドを減らすために、「ダブルチェックロッキング」を通じて「ダブルチェックロック」:オーバーヘッドを低減するために、人々の排他的な巨大なコストは、そこにある、いくつかのヒントを取ります。場合は、以下の「ダブルチェックロック」のコード
public class DoubleCheckLocking{
private static Instance instance;
public static Instance getInstance(){
if(instance == null){
synchronized(DoubleCheckLocking.class){
instance = new Instance();
}
}
return instance;
}
}
上記のコードに示すように、第1の検査部位インスタンスnullの場合、ロックおよび初期化後実行する必要はありません。大幅に低減することが可能である同期化のオーバーヘッドを。上記のコードは、前の2つの例の欠点を改善するため。
- 複数のスレッドは、同じスレッド間でオブジェクトを作成しようとし、ロックすることによって唯一のスレッドがオブジェクトを作成できることを保証するために
- オブジェクトが作成された後、再度アクセスのgetInstance()ロックオブジェクトが良いダイレクトリターンを作成されている取得する必要はありません。
しかし、問題がある「ロックダブルチェック」、instance = new Instance()
次のようにこの文は、擬似コードの3行に分割することができます。
- メモリ=()を割り当て、ストレージ・スペースの割り当て//
- ctorInstance(メモリ); //オブジェクトの初期化関数を呼び出します
- インスタンス=メモリ; //インスタンスがちょうど割り当てられたメモリアドレスを指します
擬似コードの3つのすべての行と、その後の重要な地域なので、コンパイラとプロセッサは、彼はいくつかの並べ替えを行いますので、次のシーケンスを実行することが可能です。
- メモリ割り当て=();
- インスタンス=メモリと
- ctorInstance(メモリ)。
JMMは、並べ替えを許可するので、事前発生の原則に違反しない、大学は、シングルスレッドのコードが実行可能であるが、マルチスレッドの問題を引き起こすには、そのスレッドBが完全に初期化されていないオブジェクトを読み込みます。次のようにフローチャートの実行順序は次のとおりです。
この問題を解決するには次の2つの側面から開始することができます:
- 2は無効になっていると、並べ替え操作5
- 初期設定では、他のスレッドのアクセスを許可していません。
第1の態様は、揮発性によって達成することができる、すなわち、
public class NewDoubleCheckLocking{
private volatile static Instance instance;
public static Instance getInstance(){
if(instance == null){
synchronized(NewDoubleCheckLocking.class){
instance = new Instance();
}
}
return instance;
}
}
原子および単一の操作の視認性を確保することができる揮発性キーワードを思い出して、オブジェクトを作成するプロセスを並べ替えることを回避することができます。
第二の態様は、クラス初期化によって解決することができます
public class InstanceFactory{
public static class InstanceHolder{
public static Instance instance = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.instance;
}
}
最初の呼び出しがのgetInstanceスレッド、スレッドBはまた、最初の呼び出しであると仮定される、以下が実行される概略図です。
ミューテックスのセマンティクス及びものの並べ替えたときに初期化許可、ここでは同じを得るが、他のスレッドで観察されることはありません。Java言語仕様では、各クラスまたはインタフェースCのために、ユニークな対応する初期ラッチLCを有しています。CからLCマッピングは、特定の実装にJVMによって自由を実現しています。JVMは、クラスの初期化時にこの初期のロックを取得し、各スレッドは、クラスの前に初期化されていることを確認するために少なくとも一度ロックを取得します。
第一段階:
スレッドAおよびスレッドBクラスは、同時にロックが正常にスレッドAをつかむ取得し、クラス状態を設定し、ロックを解除し、したがって以下遮断状態図に入るようにBスレッド。
第二个阶段:
线程A在释放了锁后就要开始初始化,而线程B获取到了锁,看到Class状态还是initializing,就放弃锁并进入等待状态。
第三个阶段
线程A执行完初始化,获取Class锁,将state设置为initialized,然后唤醒其他等待中的线程。
第四个阶段
线程B被唤醒,线程B尝试获取Class锁,读取到state为initialized,释放锁并访问这个类。
第五个阶段
当初始化完毕后,线程C来访问该Class,线程C获取初始化锁,读取状态,如果为initialized就释放锁,直接访问类