同期ステートメントを並べ替えることができますか。すなわち: できる:
synchronized(A) {
synchronized(B) {
......
}
}
なる :
synchronized(B) {
synchronized(A) {
......
}
}
同期ステートメントを並べ替えることができますか。すなわち: できる:
synchronized(A) {
synchronized(B) {
......
}
}
なる :
synchronized(B) {
synchronized(A) {
......
}
}
同期ステートメントを並べ替えることができますか?
synchronized
ロック順序がコードとは異なる順序で発生するように、コンパイラがブロックを並べ替えることができるかどうかを尋ねていると思います。
答えはノーだ。synchronized
ブロック (およびフィールドvolatile
アクセス) は、コンパイラに順序制限を課します。あなたの場合、別のモニター入力の前にモニター入力を移動したり、別のモニター終了後にモニター入力を移動したりすることはできません。下のグリッドを参照してください。
JSR 133 (Java Memory Model) FAQから引用するには:
たとえば、コンパイラが取得前またはリリース後にコードを移動することはできません。取得と解放がキャッシュに作用すると言うとき、考えられる多くの効果の省略形を使用しています。
Doug Lea のJSR-133 Cookbookには、並べ替えの可能性を示すグリッドがあります。グリッド内の空白のエントリは、並べ替えが許可されていることを意味します。あなたの場合、synchronized
ブロックに入るのは「MonitorEnter」(フィールドのロードと同じ並べ替えの制限volatile
)であり、ブロックを出るのは「MonitorExit」(フィールドsynchronized
への保存と同じ)です。volatile
はいといいえ。
順序は一貫している必要があります。
2 つの銀行口座間でトランザクションを作成し、常に最初に送信者のロックを取得し、次に受信者のロックを取得するとします。問題は、ダンとボブの両方が同時にお互いに送金したいとします。
スレッド 1 は、Dan の Bob へのトランザクションを処理するときに、Dan のロックを取得する可能性があります。
次に、スレッド 2 が Bob のロックを取得し、Bob のトランザクションを Dan に処理します。
その後、バム、デッドロック。
道徳は次のとおりです。
ですから、これはあなたが代わりに尋ねようとしていたかもしれない他のことを推測する答えの一部です.
JVM は、プログラムした順序と異なる順序でロックを取得することはありません。どうすればこれを知ることができますか? そうしないと、私の回答の前半の問題を解決できないからです。
同期されたステートメントは、最終的に何が起こるかに大きな影響を与えるため、コンパイラによって並べ替えられることはありません。
同期ブロックは、同期括弧の間に配置された特定のオブジェクトのロックを取得するために使用されます。
private final Object LOCK_1 = new Object();
public void foo(){
synchronized(LOCK_1){
//code here...
}
}
オブジェクト LOCK_1 のロックを取得し、同期ブロックが完了すると解放します。同期ブロックは同時アクセスを防ぐために使用されるため、複数のロックを使用する必要がある場合があります。特に、複数のスレッドセーフでないオブジェクトが読み書きされている場合は特にそうです。
ネストされた同期ブロックを使用する次のコードを検討してください。
private final Object LOCK_1 = new Object();
private final Object LOCK_2 = new Object();
public void bar(){
synchronized(LOCK_1){
//Point A
synchronized(LOCK_2){
//Point B
}
//Point C
}
//Point D
}
ポイント A、B、C、D を見ると、同期の順序が重要な理由がわかります。
最初にポイント A で LOCK_1 のロックが取得されるため、LOCK_1 を取得しようとする他のスレッドはすべてキューに入れられます。
ポイント B では、現在実行中のスレッドがLOCK_1 と LOCK_2の両方のロックを所有しています。
ポイント C で、現在実行中のスレッドがLOCK_2 のロックを解放しました。
ポイント D で、現在実行中のスレッドがすべてのロックを解放しました。
この例をひっくり返し、外側のブロックに LOCK_2 を配置することにした場合、スレッドがロックを取得する順序が変更され、最終的に何をするかに大きな影響を与えることがわかります。通常、同期ブロックを使用してプログラムを作成する場合、アクセスしているスレッドセーフでないリソースごとに 1 つの MUTEX オブジェクト (またはグループごとに 1 つの MUTEX) を使用します。LOCK_1 を使用してストリームから読み取り、LOCK_2 を使用してストリームに書き込みたいとします。ロック順序を入れ替えても同じことを意味すると考えるのは非論理的です。
LOCK_2 (書き込みロック) が別のスレッドによって保持されているとします。外部ブロックに LOCK_1 がある場合、現在実行中のスレッドは、書き込みロックのキューに入れられる前に、少なくともすべての読み取りコードを処理できます (基本的に、ポイント A でコードを実行する機能)。ロックの順序を逆にすると、現在実行中のスレッドは、書き込みが完了するのを待たなければならず、書き込みロックを保持しながら読み取りと書き込みを続行することになります (読み取り中もずっと)。
ロックの順序が入れ替わったときに発生するもう 1 つの問題 (一貫性はありませんが、最初に LOCK_1 を持つコードと最初に LOCK_2 を持つコードがあります)。2 つのスレッドが両方とも、ロック順序が異なるコードを積極的に実行しようとしているとします。スレッド 1 は外側のブロックで LOCK_1 を取得し、スレッド 2 は外側のブロックから LOCK_2 を取得します。スレッド 1 が LOCK_2 を取得しようとすると、スレッド 2 にあるため取得できません。また、スレッド 2 が LOCK_1 を取得しようとすると、スレッド 1 にあるため取得できません。2 つのスレッドは本質的に相互に永久にブロックし、デッドロック状態を形成します。
あなたの質問に答えるために、ロック間で何らかの処理を行わずに 2 つのオブジェクトをすぐにロックしたい場合、順序は関係ありません (基本的にポイント A または C での処理はありません)。ただし、デッドロックを回避するために、プログラム全体で順序を一貫させることが不可欠です。