10

いくつかの同時実行のヒントを含む素敵な記事では、例が次の行に最適化されました。

double getBalance() {
    Account acct = verify(name, password);
    synchronized(acct) { return acct.balance; }
}

私が正しく理解している場合、同期のポイントは、このスレッドによって読み取られる acct.balance の値が最新であること、および acct.balance 内のオブジェクトのフィールドへの保留中の書き込みもメイン メモリに書き込まれることを確認することです。 .

この例で少し考えさせられました: acct.balance (つまり、クラス Account のフィールド balance) を として宣言する方が効率的ではないでしょうvolatileか? これはより効率的で、synchronizeacct.balance へのすべてのアクセスを節約し、acctオブジェクト全体をロックしません。何か不足していますか?

4

3 に答える 3

14

あなたは正しいです。volatile は可視性の保証を提供します。synchronized は、保護されたコード セクションの可視性の保証とシリアル化の両方を提供します。非常に単純な状況では volatile で十分ですが、同期の代わりに volatile を使用すると簡単に問題が発生します。

アカウントに残高を調整する方法があると仮定した場合、揮発性では十分ではありません

public void add(double amount)
{
   balance = balance + amount;
}

次に、他の同期がなくバランスが不安定な場合に問題が発生します。2 つのスレッドが一緒に add() を呼び出そうとすると、次のような「見逃した」更新が発生する可能性があります。

Thread1 - Calls add(100)
Thread2 - Calls add(200)
Thread1 - Read balance (0)
Thread2 - Read balance (0)
Thread1 - Compute new balance (0+100=100)
Thread2 - Compute new balance (0+200=200)
Thread1 - Write balance = 100
Thread2 - Write balance = 200 (WRONG!)

両方のスレッドが現在の値を読み取り、個別に更新してから書き戻す (読み取り、計算、書き込み) ため、これは明らかに間違っています。volatile はここでは役に立たないため、1 つのスレッドが他のスレッドが開始する前に更新全体を完了したことを確認するために同期する必要があります。

コードを書くときに「同期の代わりに揮発性を使用できますか」と思う場合、答えは「はい」かもしれませんが、それを確実に理解するための時間/労力と、それを間違える危険性は価値がありません。利益(マイナーパフォーマンス)。

余談ですが、適切に作成された Account クラスはすべての同期ロジックを内部で処理するため、呼び出し元はそれについて心配する必要はありません。

于 2010-06-23T16:11:12.987 に答える
1

アカウントを揮発性として宣言すると、次の問題と制限が適用されます

1.「他のスレッドはローカル変数を見ることができないので、ローカル変数を揮発性と宣言することは無駄です。」さらに、メソッドで揮発性変数を宣言しようとすると、場合によってはコンパイラエラーが発生します。

double getBalance(){volatile Account acct = verify(name、password); //正しくない .. }

  1. アカウントを揮発性として宣言すると、コンパイラは、レジスタにキャッシュするのではなく、毎回新しいものをフェッチするように警告します。これにより、他のスレッドが予期せず値を変更しないと想定する特定の最適化も禁止されます。

  2. 異なるスレッドからの変数への変更を調整するために同期する必要がある場合、 volatileはアトミックアクセスを保証しません。これは、volatile変数にアクセスするとロックが保持されないため、読み取り、更新、書き込みを次のように行う場合には適していません。不可分操作。acct = verify(name、password);であることが確実でない限り。単一の不可分操作であるため、例外的な結果を保証することはできません

  3. 変数acctがオブジェクト参照である場合、nullである可能性があります。nullオブジェクトで同期しようとすると、synchronizedを使用してNullPointerExceptionがスローされます。(実際のオブジェクトではなく、参照で効果的に同期しているため)volatileが文句を言わないのに対して

  4. 代わりに、ここのようにブール変数を揮発性として宣言できます

    プライベート揮発性ブールsomeAccountflag;

    public void getBalance(){アカウントアカウント; while(!someAccountflag){acct = verify(name、password); }}

同期されたプリミティブで同期できないため、someAccountflagを同期として宣言できないことに注意してください。同期はオブジェクト変数でのみ機能しますが、プリミティブまたはオブジェクト変数は揮発性と宣言される場合があります

6.クラスの最終静的フィールドは揮発性である必要はありません。JVMがこの問題を処理します。したがって、someAccountflagは、final staticである場合、または遅延シングルトン初期化を使用してAccountをシングルトンオブジェクトとして宣言し、次のように宣言する場合でも、volatileと宣言する必要はありません。privatefinal static AccountSingleton acc_singleton = new AccountSingleton();

于 2010-08-19T00:44:07.343 に答える
1

複数のスレッドがデータを変更およびアクセスしている場合、synchronized複数のスレッド間でデータの一貫性が保証されます。

単一のスレッドがデータを変更し、複数のスレッドがデータの最新の値を読み取ろうとする場合は、volatile構造を使用します。

ただし、上記のコードでvolatileは、複数のスレッドがバランスを変更する場合、メモリの一貫性は保証されません。 type を指定したAtomicReferenceDoubleは、目的に役立ちます。

関連する SE の質問:

Javaでの揮発性と同期の違い

于 2016-11-06T16:19:31.477 に答える