Johnのコメントの1つに同意します。変数の参照が変更された場合の不整合を防ぐために、非最終変数にアクセスするときは常に最終ロックダミーを使用する必要があります。したがって、どのような場合でも、最初の経験則として:
ルール#1:フィールドが非ファイナルの場合、常に(プライベート)ファイナルロックダミーを使用します。
理由#1:ロックを保持し、変数の参照を自分で変更します。同期ロックの外側で待機している別のスレッドは、保護されたブロックに入ることができます。
理由#2:ロックを保持し、別のスレッドが変数の参照を変更します。結果は同じです。別のスレッドが保護されたブロックに入ることができます。
ただし、ファイナルロックダミーを使用する場合は、別の問題があります。非同期オブジェクトは、synchronize(object)を呼び出すときにのみ、RAMと同期されるため、間違ったデータを取得する可能性があります。したがって、2番目の経験則として:
ルール#2:非最終オブジェクトをロックするときは、常に次の両方を行う必要があります。RAM同期のために、最終ロックダミーと非最終オブジェクトのロックを使用します。(唯一の代替手段は、オブジェクトのすべてのフィールドを揮発性として宣言することです!)
これらのロックは「ネストされたロック」とも呼ばれます。それらを常に同じ順序で呼び出す必要があることに注意してください。そうしないと、デッドロックが発生します。
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
ご覧のとおり、2つのロックは常に一緒に属しているため、同じ行に直接書き込みます。このように、10個のネストロックを実行することもできます。
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
synchronized (LOCK3)
別のスレッドのように内部ロックを取得しただけでは、このコードは壊れないことに注意してください。しかし、次のような別のスレッドを呼び出すと、壊れます。
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
非最終フィールドを処理する際のこのようなネストされたロックを回避する回避策は1つだけです。
ルール#2-代替:オブジェクトのすべてのフィールドを揮発性として宣言します。(ここでは、これを行うことの欠点については説明しません。たとえば、読み取りの場合でもxレベルのキャッシュにストレージを保存できないなどです。)
したがって、aioobeは非常に正しいです。java.util.concurrentを使用するだけです。または、同期に関するすべてを理解し始め、ネストされたロックを使用して自分でそれを行います。;)
非最終フィールドでの同期が失敗する理由の詳細については、私のテストケースをご覧ください:https://stackoverflow.com/a/21460055/2012947
また、RAMとキャッシュが原因で同期が必要な理由の詳細については、https://stackoverflow.com/a/21409975/2012947をご覧ください。
o
、同期ブロックに達した時に参照しました。o
参照するオブジェクトが変更された場合、別のスレッドがやって来て、同期されたコードブロックを実行できます。