web-dev-qa-db-ja.com

ブール値で同期するのはなぜ良い習慣ではないのですか?

私の建築家はいつもそう言っています

ブール値で同期しない

理由が分からないので、良い例ではない理由を例を挙げて説明していただければ幸いです。 参考サンプルコード

private Boolean isOn = false;
private String statusMessage = "I'm off";
public void doSomeStuffAndToggleTheThing(){

   // Do some stuff
   synchronized(isOn){
      if(isOn){
         isOn = false;
         statusMessage = "I'm off";
         // Do everything else to turn the thing off
      } else {
         isOn = true;
         statusMessage = "I'm on";
         // Do everything else to turn the thing on
      }
   }
}
33
Rachel

「ブール値で同期しない」理由を理解できません

定数オブジェクトインスタンスでは常にsynchronizeを使用する必要があります。割り当てているオブジェクトで同期した場合(つまり、オブジェクトを新しいオブジェクトに変更した場合)は一定ではなく、異なるオブジェクトで異なるスレッドが同期していますinstances。異なるオブジェクトインスタンスで同期しているため、複数のスレッドが同時に保護ブロックに入り、競合状態が発生します。これは、LongIntegerなどで同期する場合と同じ答えです。

// this is not final so it might reference different objects
Boolean isOn;
...
synchronized (isOn) {
   if (isOn) {
      // this changes the synchronized object isOn to another object
      // so another thread can then enter the synchronized with this thread
      isOn = false;

さらに悪いことに(@McDowellが指摘したように)オートボクシング(isOn = true)によって作成されたBooleanは、ClassLoaderのシングルトンであるBoolean.TRUE(または.FALSE)と同じオブジェクトです。 すべてのオブジェクト。ロックオブジェクトは、それが使用されているクラスに対してローカルである必要があります。そうでない場合、他のクラスが同じ間違いを犯している場合に他のクラスが他のロックケースでロックしている可能性があるのと同じシングルトンオブジェクトをロックします。

ブール値をロックする必要がある場合の適切なパターンは、private finalロックオブジェクトを定義することです。

private final Object lock = new Object();
...

synchronized (lock) {
   ...

または、AtomicBooleanオブジェクトを使用することも検討する必要があります。つまり、synchronizeをまったく使用する必要がない可能性があります。

private final AtomicBoolean isOn = new AtomicBoolean(false);
...

// if it is set to false then set it to true, no synchronization needed
if (isOn.compareAndSet(false, true)) {
    statusMessage = "I'm now on";
} else {
    // it was already on
    statusMessage = "I'm already on";
}

あなたのケースでは、スレッドでオン/オフを切り替える必要があるように見えるので、synchronizeオブジェクトでlockを引き続き実行し、ブール値を設定して、テスト/設定の競合状態を回避する必要があります。

synchronized (lock) {
    if (isOn) {
        isOn = false;
        statusMessage = "I'm off";
        // Do everything else to turn the thing off
    } else {
        isOn = true;
        statusMessage = "I'm on";
        // Do everything else to turn the thing on
    }
}

最後に、statusMessageが他のスレッドからアクセスされることが予想される場合は、取得中にvolatileを使用しない限り、synchronizeとしてマークする必要があります。

61
Gray
private Boolean isOn = false;
public void doSomeStuffAndToggleTheThing(){
   synchronized(isOn){

これはひどい考えです。 isOnは、公開されているBoolean.FALSEと同じオブジェクトを参照します。不適切に記述されたコードの他の部分もこのオブジェクトをロックすると決定した場合、2つの完全に関連のないトランザクションが互いに待機する必要があります。

ロックは オブジェクトインスタンス で実行され、それらを参照する変数では実行されません。

enter image description here

18
McDowell

すべてのラッパークラスは不変です。それらを同期してはいけない主な理由の1つ。

2つのスレッドがラッパークラスオブジェクトで同期し、そのうちの1つがその値を変更するかのように、2つのスレッドが新しい/変更されたオブジェクトで同期され、両方のスレッドが2つの異なるオブジェクトで同期されます。したがって、同期の目的全体が失われます。

1
shrishti

私はあなたの問題はブールの同期よりもそれ自体の同期にあると思います。各スレッドが道路であり、ステートメント(車)が次々に移動するとします。ある時点で交差点が存在する可能性があります。セマフォがなければ、衝突が発生する可能性があります。 Java言語には、これを説明するための組み込みの方法があります。どのオブジェクトも共通部分になる可能性があるため、どのオブジェクトにもセマフォとして機能するモニターが関連付けられています。コードで同期を使用すると、セマフォを作成するため、すべての道路(スレッド)に同じセマフォを使用する必要があります。ブール値が2つしか存在しないため、この問題は実際にはブール固有ではありません。この問題は、インスタンス変数を同期して同じポイントを指すたびに発生します別のオブジェクトへの変数なので、コードはブール値では間違っていますが、何が起こっているのか理解していない場合、整数、文字列、その他のオブジェクトでも同様に危険です。

1
Raffaele