web-dev-qa-db-ja.com

ゲッターとセッターを同期する必要がありますか?

private double value;

public synchronized void setValue(double value) {
    this.value = value;
}
public double getValue() {
    return this.value;
}

上記の例では、ゲッターを同期させる点がありますか?

55
DD.

Java Concurrency in Practice ここを引用するのが最善だと思います:

共有変数への書き込み時にのみ同期を使用する必要があると仮定するのはよくある間違いです。これは単に真実ではありません。

複数のスレッドがアクセスできる可変状態変数ごとに、その変数へのすべてのアクセスは、同じロックを保持した状態で実行する必要があります。この場合、変数はそのロックによって保護されていると言います。

同期がない場合、コンパイラ、プロセッサ、およびランタイムは、操作が実行されているように見える順序に対して、まったく奇妙なことを実行できます。同期が不十分なマルチスレッドプログラムでメモリアクションが「必ず」発生する順序について推論しようとすると、ほぼ間違いなく正しくありません。

通常、プリミティブにそれほど注意する必要はありません。したがって、これがintまたはbooleanの場合、次のようになります。

スレッドが同期せずに変数を読み取ると、古い値が表示される場合がありますが、少なくとも、ランダムな値ではなく、スレッドによって実際に配置された値が表示されます。

ただし、これはlongまたはdoubleで宣言されていない場合のvolatileなどの64ビット操作には当てはまりません。

Javaメモリモデルでは、フェッチおよびストア操作がアトミックである必要がありますが、不揮発性のlongおよびdouble変数の場合、JVMは64ビットの読み取りまたは書き込みを2つの別個の32ビット操作として扱うことができます。したがって、読み取りと書き込みが異なるスレッドで発生する場合、不揮発性の長いデータを読み取り、ある値の上位32ビットと別の値の下位32ビットを取得することができます。

したがって、たとえ古い値を気にしなくても、揮発性であると宣言されているか、ロックで保護されていない限り、マルチスレッドプログラムで共有可変longおよびdouble変数を使用することは安全ではありません。

73
Konrad Reiche

JITがコードをコンパイルする合法的な方法を例を挙げて説明します。あなたが書く:

_while (myBean.getValue() > 1.0) {
  // perform some action
  Thread.sleep(1);
}
_

JITコンパイル:

_if (myBean.getValue() > 1.0) 
  while (true) {
    // perform some action
    Thread.sleep(1);
  }
_

わずかに異なるシナリオでは、Javaコンパイラーが同様のバイトコードを誇示することができます(異なるgetValueへの動的ディスパッチの可能性を排除するだけでよいでしょう)。これは教科書の例です。巻き上げの。

なぜこれが合法なのですか?コンパイラは、上記のコードの実行中にmyBean.getValue()の結果が決して変化しないと想定する権利を持っています。 synchronizedがなければ、他のスレッドによるアクションを無視できます。

16
Marko Topolnik

ここでの理由は、スレッドが読み取り中に値を更新する他のスレッドを防ぐため、古い値に対するアクションの実行を避けるためです。

ここで、getメソッドは「this」の組み込みロックを取得するため、setterメソッドを使用して設定/更新を試みる他のスレッドは、「this」のロックを取得して、getを実行するスレッドによって既に取得されたsetterメソッドに入る必要があります。

これが、可変状態で操作を実行するときに同じロックを使用する慣習に従うことが推奨される理由です。

複合ステートメントがないため、フィールドを揮発性にすることはここで機能します。


同期メソッドは、「this」である固有ロックを使用することに注意することが重要です。そのため、両方を同期して取得および設定するということは、メソッドに入るスレッドがこのロックを取得する必要があることを意味します。


非アトミック64ビット操作を実行する場合、特別な考慮が必要です。 Java Concurrency In Practiceからの抜粋は、ここで状況を理解するのに役立ちます。

「Javaメモリモデルはフェッチおよびストア操作がアトミックである必要がありますが、不揮発性のlongおよびdouble変数の場合、JVMは64ビットの読み取りまたは書き込みを2つの独立した32ビットとして扱うことができますしたがって、読み取りと書き込みが異なるスレッドで発生する場合、不揮発性の長いデータを読み取り、ある値の上位32ビットと別の値の下位32ビットを取得することができます。古い値については、揮発性と宣言されているか、ロックで保護されていない限り、マルチスレッドプログラムで共有の可変long変数とdouble変数を使用するのは安全ではありません。

1

誰かにとってこのコードはひどいように見えるかもしれませんが、非常にうまく機能しています。

  private Double value;
  public  void setValue(Double value){
    updateValue(value, true);
  }
  public Double getValue(){
      return updateValue(value, false);
  }
  private double updateValue(Double value,boolean set){
    synchronized(MyClass.class){
      if(set)
        this.value = value;
      return value;
    }
  }
0
Adam111p