19

インスタンス メンバーへのアクセスを同期する必要があるのはどのような場合ですか? クラスの静的メンバーへのアクセスは常に同期する必要があることを理解しています。これは、クラスのすべてのオブジェクト インスタンスで共有されるためです。

私の質問は、インスタンス メンバーを同期しない場合、いつ間違っているのでしょうか?

たとえば、私のクラスが

public class MyClass {
    private int instanceVar = 0;

    public setInstanceVar()
    {
        instanceVar++;
    }

    public getInstanceVar()
    {
        return instanceVar;
    }
}

どのような場合に (クラスの使用についてMyClass)メソッドを持つ 必要public synchronized setInstanceVar()があり public synchronized getInstanceVar()ますか?

ご回答ありがとうございます。

4

6 に答える 6

39

synchronized修飾子は本当に悪い考えであり、絶対に避けるべきです。Sun がロックを達成しやすくしようと試みたことは賞賛に値すると思いますが、そのsynchronized価値以上に多くの問題を引き起こすだけです。

問題は、synchronizedメソッドが実際にはロックを取得しthis、メソッドの期間中それを保持するための単なる構文糖衣であることです。したがって、public synchronized void setInstanceVar()次のようなものと同等になります。

public void setInstanceVar() {
    synchronized(this) {
        instanceVar++;
    }
}

これは、次の 2 つの理由で好ましくありません。

  • 同じクラス内のすべてのsynchronizedメソッドがまったく同じロックを使用するため、スループットが低下します
  • 他のクラスのメンバーを含め、誰でもロックにアクセスできます。

私が別のクラスでこのようなことをするのを妨げるものは何もありません:

MyClass c = new MyClass();
synchronized(c) {
    ...
}

そのブロック内で、 内のすべてのメソッドでsynchronized必要なロックを保持しています。これにより、スループットがさらに低下し、デッドロックの可能性が劇的に増加します。synchronizedMyClass

より良いアプローチは、専用の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 でも同じです)。

于 2008-11-21T18:18:50.150 に答える
20

クラスをスレッドセーフにするかどうかによって異なります。ほとんどのクラスは (簡単にするために) スレッドセーフであってはなりません。その場合、同期は必要ありません。スレッドセーフにする必要がある場合は、アクセスを同期する、変数を揮発性にする必要があります。(他のスレッドが「古い」データを取得するのを回避します。)

于 2008-11-21T18:01:27.690 に答える
3

このクラスをスレッド セーフにしたい場合は、メモリから常に最新の値を取得するように宣言instanceVarします。また、JVM ではインクリメントはアトミック操作ではないため、これを作成します。volatilesetInstanceVar() synchronized

private volatile int instanceVar =0;

public synchronized setInstanceVar() { instanceVar++;

}
于 2008-11-21T18:08:27.940 に答える
1

. 大まかに言えば、答えは「場合による」です。ここでセッターとゲッターを同期することは、複数のスレッドが互いのインクリメント操作の間で変数を読み取れないことを保証するという意図された目的のみを持っています。

 synchronized increment()
 { 
       i++
 }

 synchronized get()
 {
   return i;
  }

ただし、ここでは実際には機能しません。呼び出し元のスレッドがインクリメントされたのと同じ値を取得することを保証するには、原子的にインクリメントしてから取得していることを保証する必要がありますが、ここでは行っていません-つまり、次のようなことをしなければなりません

  synchronized int {
    increment
    return get()
  }

基本的に、同期は、どの操作がスレッドセーフで実行されることを保証する必要があるかを定義するのに役立ちます (つまり、別のスレッドが操作を弱体化させ、クラスを非論理的に動作させたり、期待するデータの状態を弱めたりする状況を作成することはできません)することが)。実際には、ここで扱うよりも大きなトピックです。

この本Java Concurrency in Practiceは優れており、確かに私よりもはるかに信頼性があります。

于 2008-11-21T18:15:10.687 に答える
1

簡単に言えば、オブジェクト/アプリケーションの状態を変更する同じインスタンスの同じメソッドにアクセスする複数のスレッドがある場合は、同期を使用します。

これは、スレッド間の競合状態を防ぐための簡単な方法として意図されており、実際には、グローバル オブジェクトなどの同じインスタンスに同時スレッドがアクセスすることを計画している場合にのみ使用する必要があります。

並行スレッドでオブジェクトのインスタンスの状態を読み取る場合、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;
    }
}
于 2008-11-21T18:46:26.240 に答える
-3

Java では、int に対する操作はアトミックであるため、必要ありません。この場合、一度に 1 つの書き込みと 1 つの読み取りだけを行っている場合は、同期する必要はありません。

これらが long または double の場合、long/double の一部が更新され、別のスレッドが読み取られ、最後に long/double の他の部分が更新される可能性があるため、同期する必要があります。

于 2008-11-21T18:04:32.363 に答える