申し訳ありませんが、これはとても長い質問です。
私は最近、マルチスレッドを個人的なプロジェクトにゆっくりと実装するため、マルチスレッドについて多くの研究を行ってきました。ただし、おそらくわずかに不正確な例が豊富にあるため、特定の状況での同期ブロックの使用とボラティリティは、私にはまだ少し不明確です。
私の中心的な質問はこれです:スレッドが同期されたブロック内にあるとき、参照とプリミティブへの変更は自動的に揮発性です(つまり、キャッシュではなくメインメモリで実行されます)、または読み取りも同期する必要がありますか?ちゃんと?
- もしそうなら、単純なゲッターメソッドを同期する目的は何ですか?(例1を参照)また、スレッドが何かで同期している限り、すべての変更がメインメモリに送信されますか?たとえば、非常に高レベルの同期内のあらゆる場所で大量の作業を行うために送信された場合、すべての変更はメインメモリに行われ、再びロックが解除されるまでキャッシュには何も行われませんか?
- そうでない場合、変更は同期ブロック内で明示的に行う必要がありますか、それともJavaは、たとえばLockオブジェクトの使用を実際に取得できますか?(例3を参照)
- 同期されたオブジェクトは、何らかの方法で変更されている参照/プリミティブに関連付ける必要がありますか(たとえば、それを含む直接のオブジェクト)?あるオブジェクトを同期して書き込み、それ以外の場合は安全であれば別のオブジェクトと読み取ることはできますか?(例2を参照)
(以下の例では、同期されたメソッドと同期された(これ)が嫌われていることとその理由を知っていますが、それについての議論は私の質問の範囲を超えています)
例1:
class Counter{
int count = 0;
public synchronized void increment(){
count++;
}
public int getCount(){
return count;
}
}
この例では、++はアトミック操作ではないため、increment()を同期する必要があります。そのため、2つのスレッドが同時に増加すると、全体でカウントが1増加する可能性があります。カウントプリミティブはアトミックである必要があり(たとえば、long / double / referenceではない)、それで問題ありません。
getCount()をここで同期する必要がありますか?その理由は正確ですか?私が最もよく聞いた説明は、返されるカウントがインクリメント前かインクリメント後かを保証するものではないということです。しかし、これは少し違う何かの説明のように思えます、それはそれ自体が間違った場所にあることに気づきました。つまり、getCount()を同期したとしても、保証はありません。実際の読み取りが実際の書き込みの前後であるかどうかがわからないので、ロックの順序がわからないことになります。
例2:
次の例は、ここに示されていないトリックを通じて、これらのメソッドのいずれも同時に呼び出されることはないと想定した場合、スレッドセーフですか?毎回ランダムな方法を使用してカウントが増加し、その後適切に読み取られる場合、カウントは期待どおりに増加しますか、それともロックは同じオブジェクトである必要がありますか?(ところで、私はこの例がいかにばかげているかを完全に理解していますが、私は実践よりも理論に興味があります)
class Counter{
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private final Object lock3 = new Object();
int count = 0;
public void increment1(){
synchronized(lock1){
count++;
}
}
public void increment2(){
synchronized(lock2){
count++;
}
}
public int getCount(){
synchronized(lock3){
return count;
}
}
}
例3:
起こる前の関係は単にJavaの概念ですか、それともJVMに組み込まれている実際のものですか?この次の例では、概念的な発生前の関係を保証できますが、Javaは、組み込みのものであれば、それを理解するのに十分賢いですか?そうではないと思いますが、この例は実際にはスレッドセーフですか?スレッドセーフの場合、getCount()がロックしなかった場合はどうでしょうか。
class Counter{
private final Lock lock = new Lock();
int count = 0;
public void increment(){
lock.lock();
count++;
lock.unlock();
}
public int getCount(){
lock.lock();
int count = this.count;
lock.unlock();
return count;
}
}