10 Javaマルチスレッドプログラミング環境における単一モードの実現
開発では、インスタンスの作成に多くのシステムリソースを消費する必要がある場合、通常、不活性なロードメカニズムを使用します.すなわち、このインスタンスを使用する場合にのみ作成されます.この利点は、単一のインスタンスモードで広く応用されています.このメカニズムはsingle-threaded環境での実現は非常に簡単であるが、multi-threaded環境では危険性がある.本論文では,不活性ロードメカニズムとマルチスレッド環境でのその使用方法に重点を置いた.(著者numberzero、IBMの文章「Double-checked locking and the Singleton pattern」を参照して、転載と討論を歓迎する)1単例モードの遅延ロード
通常、単一のクラスを設計すると、クラスの内部にクラスが構築され、static getInstanceメソッドが単一のオブジェクトを取得する方法を提供します.例:
Singleton.java
このようなコードの欠点は、クラスを最初にロードするときにSingletonインスタンスを作成することです.このような結果は、インスタンスを作成するときにこのインスタンスが必要ではない可能性があるため、私たちが望んでいるのとは異なります.同時に、このSingletonインスタンスの作成がシステムリソースを非常に消費し、アプリケーションが常にSingletonインスタンスを使用していない場合、Singletonを作成するために消費されるシステムリソースは無駄になります.このような状況を回避するために、通常、遅延ロードのメカニズムを使用します.つまり、使用時に作成されます.上記のコードの遅延ロードコードは以下の通りです:Singleton.java
これにより、Singleton.getInstance()を初めて呼び出すと、このインスタンスが作成され、後で再呼び出すときはこのインスタンスだけを返すことができます.
2マルチスレッドでの遅延ロードの問題は、まず遅延ロードのコードを抽出します.
これは、2つのスレッドAとBが同時にこの方法を実行した場合、以下のように実行される.
1.Aはif判定に入り、このときinstanceはnullであるためif内に入る2.Bはif判定に入り、このときAはまだinstanceが作成されていないためinstanceもnullであるため、Bもif内に入る3.Aはinstanceを作成して戻る4.Bもinstanceを作成して戻る
この時点で問題が発生し、私たちの単一の例は2回作成されましたが、これは私たちが望んでいるものではありません.
3様々な解決策とその存在する問題3.1 Classロックメカニズムを使用する以上の問題の最も直感的な解決策は、getInstanceメソッドにsynchronize接頭辞を付けることであり、getInstanceメソッドを毎回1つのスレッドだけで呼び出すことができる.
この解決策は確かにエラーの発生を防止することができますが、getInstanceメソッドを呼び出すたびにパフォーマンスに影響します.
Singletonのロックは、実際には、単一のインスタンスが作成された後、その後のリクエストが反発メカニズムを使用する必要はありません.
3.2 double-checked locking上記の問題を解決するためにdouble-checked lockingのソリューションJavaコードを提案したことがある
このコードがどのように動作しているかを見てみましょう.まず、スレッドがリクエストを発行すると、instanceがnullであるかどうかを確認し、そうでない場合はその内容を直接返し、synchronizedブロックに入るのにかかるリソースを回避します.次に、第2節で述べた場合でも、2つのスレッドが同時に第1のif判定に入った場合、synchronizedブロックのコードを順番に実行する必要があり、第1のコードブロックに入ったスレッドは新しいSingletonインスタンスを作成し、後続のスレッドはif判定に合格できないため、余分なインスタンスを作成しない.
上記の説明は、私たちが直面しているすべての問題を解決したようですが、実際には、JVMの観点から、これらのコードにエラーが発生する可能性があります.JVMでは、Javaコマンドが実行されます.Java命令におけるオブジェクトの作成と付与操作は別々に行われる,すなわちinstance=new Singleton()である.文は2つのステップに分けて実行されます.しかし、JVMは、この2つの操作の前後順序を保証しません.つまり、JVMが新しいSingletonインスタンスに空間を割り当て、instanceメンバーに直接値を割り当ててから、このSingletonインスタンスを初期化する可能性があります.これによりエラーが可能となり、A、Bの2つのスレッドを例にとると、1.A、Bスレッドは同時に最初のif判断に入った2.Aはまずsynchronizedブロックに入り、instanceはnullであるため、instance=new Singleton()を実行する.3.JVM内部の最適化メカニズムのため、JVMはまずSingletonインスタンスに割り当てられた空きメモリをいくつか描き、instanceメンバーに値を割り当て(JVMがこのインスタンスの初期化を開始していないことに注意)、Aはsynchronizedブロックを離れた.4.Bはsynchronizedブロックに入り、instanceはnullではないため、synchronizedブロックをすぐに離れ、その結果をメソッドを呼び出すプログラムに返す.5.このときBスレッドはSingletonインスタンスを使用しようとしたが、初期化されていないことに気づき、エラーが発生した.4内部クラスを介してマルチスレッド環境における単一のモードを実現遅延ロードを実現するために、getInstanceを呼び出すたびに反発して実行する必要がなく、最も便利な解決策は以下の通りである:Javaコード
JVM内部のメカニズムは、クラスがロードされると、このクラスのロードプロセスがスレッド反発することを保証することができる.これにより、getInstanceを初めて呼び出すと、JVMはinstanceが一度だけ作成されることを保証し、instanceに割り当てられたメモリの初期化が完了することを保証し、3.2の問題を心配する必要はありません.さらに、この方法は、最初の呼び出し時に反発メカニズムのみを使用し、3.1の非効率的な問題を解決する.最後にinstanceはSingletonContainerクラスを最初にロードしたときに作成され、SingletonContainerクラスは呼び出されます.
getInstanceメソッドの場合のみロードされるため、遅延ロードも実現
通常、単一のクラスを設計すると、クラスの内部にクラスが構築され、static getInstanceメソッドが単一のオブジェクトを取得する方法を提供します.例:
Singleton.java
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
このようなコードの欠点は、クラスを最初にロードするときにSingletonインスタンスを作成することです.このような結果は、インスタンスを作成するときにこのインスタンスが必要ではない可能性があるため、私たちが望んでいるのとは異なります.同時に、このSingletonインスタンスの作成がシステムリソースを非常に消費し、アプリケーションが常にSingletonインスタンスを使用していない場合、Singletonを作成するために消費されるシステムリソースは無駄になります.このような状況を回避するために、通常、遅延ロードのメカニズムを使用します.つまり、使用時に作成されます.上記のコードの遅延ロードコードは以下の通りです:Singleton.java
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == instance)
instance = new Singleton();
return instance;
}
}
これにより、Singleton.getInstance()を初めて呼び出すと、このインスタンスが作成され、後で再呼び出すときはこのインスタンスだけを返すことができます.
2マルチスレッドでの遅延ロードの問題は、まず遅延ロードのコードを抽出します.
public static Singleton getInstance() {
if (null == instance)
instance = new Singleton();
return instance;
}
これは、2つのスレッドAとBが同時にこの方法を実行した場合、以下のように実行される.
1.Aはif判定に入り、このときinstanceはnullであるためif内に入る2.Bはif判定に入り、このときAはまだinstanceが作成されていないためinstanceもnullであるため、Bもif内に入る3.Aはinstanceを作成して戻る4.Bもinstanceを作成して戻る
この時点で問題が発生し、私たちの単一の例は2回作成されましたが、これは私たちが望んでいるものではありません.
3様々な解決策とその存在する問題3.1 Classロックメカニズムを使用する以上の問題の最も直感的な解決策は、getInstanceメソッドにsynchronize接頭辞を付けることであり、getInstanceメソッドを毎回1つのスレッドだけで呼び出すことができる.
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
この解決策は確かにエラーの発生を防止することができますが、getInstanceメソッドを呼び出すたびにパフォーマンスに影響します.
Singletonのロックは、実際には、単一のインスタンスが作成された後、その後のリクエストが反発メカニズムを使用する必要はありません.
3.2 double-checked locking上記の問題を解決するためにdouble-checked lockingのソリューションJavaコードを提案したことがある
public static Singleton getInstance() {
if (null== instance)
synchronized (instance) {
if (null== instance)
instance = new Singleton();
}
return instance;
}
このコードがどのように動作しているかを見てみましょう.まず、スレッドがリクエストを発行すると、instanceがnullであるかどうかを確認し、そうでない場合はその内容を直接返し、synchronizedブロックに入るのにかかるリソースを回避します.次に、第2節で述べた場合でも、2つのスレッドが同時に第1のif判定に入った場合、synchronizedブロックのコードを順番に実行する必要があり、第1のコードブロックに入ったスレッドは新しいSingletonインスタンスを作成し、後続のスレッドはif判定に合格できないため、余分なインスタンスを作成しない.
上記の説明は、私たちが直面しているすべての問題を解決したようですが、実際には、JVMの観点から、これらのコードにエラーが発生する可能性があります.JVMでは、Javaコマンドが実行されます.Java命令におけるオブジェクトの作成と付与操作は別々に行われる,すなわちinstance=new Singleton()である.文は2つのステップに分けて実行されます.しかし、JVMは、この2つの操作の前後順序を保証しません.つまり、JVMが新しいSingletonインスタンスに空間を割り当て、instanceメンバーに直接値を割り当ててから、このSingletonインスタンスを初期化する可能性があります.これによりエラーが可能となり、A、Bの2つのスレッドを例にとると、1.A、Bスレッドは同時に最初のif判断に入った2.Aはまずsynchronizedブロックに入り、instanceはnullであるため、instance=new Singleton()を実行する.3.JVM内部の最適化メカニズムのため、JVMはまずSingletonインスタンスに割り当てられた空きメモリをいくつか描き、instanceメンバーに値を割り当て(JVMがこのインスタンスの初期化を開始していないことに注意)、Aはsynchronizedブロックを離れた.4.Bはsynchronizedブロックに入り、instanceはnullではないため、synchronizedブロックをすぐに離れ、その結果をメソッドを呼び出すプログラムに返す.5.このときBスレッドはSingletonインスタンスを使用しようとしたが、初期化されていないことに気づき、エラーが発生した.4内部クラスを介してマルチスレッド環境における単一のモードを実現遅延ロードを実現するために、getInstanceを呼び出すたびに反発して実行する必要がなく、最も便利な解決策は以下の通りである:Javaコード
public class Singleton {
private Singleton() {
}
private static class SingletonContainer {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonContainer.instance;
}
}
JVM内部のメカニズムは、クラスがロードされると、このクラスのロードプロセスがスレッド反発することを保証することができる.これにより、getInstanceを初めて呼び出すと、JVMはinstanceが一度だけ作成されることを保証し、instanceに割り当てられたメモリの初期化が完了することを保証し、3.2の問題を心配する必要はありません.さらに、この方法は、最初の呼び出し時に反発メカニズムのみを使用し、3.1の非効率的な問題を解決する.最後にinstanceはSingletonContainerクラスを最初にロードしたときに作成され、SingletonContainerクラスは呼び出されます.
getInstanceメソッドの場合のみロードされるため、遅延ロードも実現