Javaで同期メソッドアクセスが必要なケースは?
-
08-07-2019 - |
質問
どのような場合にインスタンスメンバーへのアクセスを同期する必要がありますか? クラスのすべてのオブジェクトインスタンス間で共有されるため、クラスの静的メンバーへのアクセスは常に同期する必要があることを理解しています。
私の質問は、インスタンスメンバーを同期しない場合、いつ不正になるのですか?
たとえば私のクラスが
の場合public class MyClass {
private int instanceVar = 0;
public setInstanceVar()
{
instanceVar++;
}
public getInstanceVar()
{
return instanceVar;
}
}
( MyClass
クラスを使用する場合)どのような場合にメソッドが必要か
public synchronized setInstanceVar()
および
public synchronized getInstanceVar()
?
ご回答いただきありがとうございます。
解決
クラスをスレッドセーフにするかどうかによって異なります。ほとんどのクラスは、スレッドセーフ(単純化のため)であってはなりません。その場合、同期は必要ありません。スレッドセーフにする必要がある場合は、アクセスを同期するか、変数を揮発性にする必要があります。 (他のスレッドが「古くなった」データを取得するのを防ぎます。)
他のヒント
synchronized
修飾子は本当に悪いアイデアであり、どんな場合でも避けるべきです。 Sunがロックを少し簡単に実現しようとしたことは称賛に値すると思いますが、同期
は価値がある以上のトラブルを引き起こすだけです。
問題は、 synchronized
メソッドは、実際には this
のロックを取得し、メソッドの継続中保持するための単なる構文シュガーであるということです。したがって、 public synchronized void setInstanceVar()
は次のようになります。
public void setInstanceVar() {
synchronized(this) {
instanceVar++;
}
}
これは次の2つの理由で悪いです:
- 同じクラス内のすべての
synchronized
メソッドはまったく同じロックを使用するため、スループットが低下します - 誰でも他のクラスのメンバーを含むロックにアクセスできます。
別のクラスでこのようなことをするのを妨げるものは何もありません:
MyClass c = new MyClass();
synchronized(c) {
...
}
synchronized
ブロック内で、 MyClass
内のすべての synchronized
メソッドに必要なロックを保持しています。これによりスループットがさらに低下し、劇的にデッドロックが発生する可能性が高くなります。
より良いアプローチは、専用の lock
オブジェクトを用意し、 synchronized(...)
ブロックを直接使用することです。
public class MyClass {
private int instanceVar;
private final Object lock = new Object(); // must be final!
public void setInstanceVar() {
synchronized(lock) {
instanceVar++;
}
}
}
または、 java.util.concurrent.Lock
インターフェースと java.util.concurrent.locks.ReentrantLock
実装を使用して、基本的に同じ結果を実現できます(実際、Java 6)でも同じです。
このクラスをスレッドセーフにしたい場合は、 instanceVar
を volatile
として宣言して、メモリから常に最新の値を取得するようにします。また、 JVMでは増分はアトミック操作ではないため、 setInstanceVar()
synchronized
。
private volatile int instanceVar =0;
public synchronized setInstanceVar() { instanceVar++;
}
。おおよそ、答えは「依存する」です。ここでセッターとゲッターを同期することは、複数のスレッドが互いのインクリメント操作の間で変数を読み取れないことを保証することのみを目的としています:
synchronized increment()
{
i++
}
synchronized get()
{
return i;
}
しかし、実際にはここでも動作しません。呼び出し元のスレッドがインクリメントした値と同じ値を取得することを保証するために、アトミックにインクリメントしてから取得することを保証する必要があります。 -つまり、次のようなことをする必要があります
synchronized int {
increment
return get()
}
基本的に、同期は、スレッドセーフを実行するために保証する必要がある操作を定義するのに役立ちます(つまり、別のスレッドが操作を弱体化させ、クラスを非論理的に動作させたり、期待する状態を弱体化させる状況を作成することはできませんデータの))。実際には、ここで説明できるよりも大きなトピックです。
この本 Java同時実行の実践は優れており、確かにそれ以上です私よりも信頼できる。
簡単に言えば、オブジェクト/アプリケーションの状態を変更する同じインスタンスの同じメソッドに複数のスレッドがアクセスしている場合にsynchronizedを使用します。
これは、スレッド間の競合状態を防ぐ簡単な方法として意図されており、実際には、グローバルオブジェクトなどの同じインスタンスに同時スレッドがアクセスすることを計画している場合にのみ使用する必要があります。
現在、並行スレッドを使用してオブジェクトのインスタンスの状態を読み取る場合、java.util.concurrent.locks.ReentrantReadWriteLockを調べることができます-理論的には、一度に多くのスレッドが読み取ることができます、ただし、書き込みが許可されるスレッドは1つだけです。だから、誰もが与えているように見えるゲッターと設定方法の例では、次のことができます:
public class MyClass{
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private int myValue = 0;
public void setValue(){
rwl.writeLock().lock();
myValue++;
rwl.writeLock().unlock();
}
public int getValue(){
rwl.readLock.lock();
int result = myValue;
rwl.readLock.unlock();
return result;
}
}
Javaでは、intの操作はアトミックなので、この場合、一度に1つの書き込みと1つの読み取りを行うだけであれば、同期する必要はありません。
これらがlongまたはdoubleの場合、long / doubleの一部を更新し、別のスレッドを読み取らせてから、最後にlong / doubleのその他の部分を更新できるため、同期する必要があります。