5

スレッドセーフであると主張されている Java クラスの例を以下に見つけました。スレッドセーフになる方法を誰か説明してもらえますか? クラスの最後のメソッドが、リーダー スレッドの同時アクセスに対して保護されていないことがはっきりとわかります。または、ここで何か不足していますか?

public class Account {
    private Lock lock = new ReentrantLock();
    private int value = 0;
    public void increment() {
       lock.lock();
       value++;
       lock.unlock();
    }
    public void decrement() {
       lock.lock();
       value--;
       lock.unlock();
    }
    public int getValue() {
       return value;
    }
}
4

6 に答える 6

6

コードはスレッドセーフではありません。

1 つのスレッドが を呼び出しdecrement、次に 2 番目のスレッドが を呼び出すとしますgetValue。何が起こるのですか?

問題は、と の間に「前に起こる」関係がないことです。これは、呼び出しが の結果を見るという保証がないことを意味します。実際、 は、と の不定シーケンスの呼び出しの結果を「見逃す」可能性があります。decrementgetValuegetValuedecrementgetValueincrementdecrement

実際、クラスを使用するコードを見ない限りAccount、スレッドセーフの問題は明確に定義されていません。プログラムのスレッドセーフ1の従来の概念は、スレッド関連の非決定性に関係なく、コードが正しく動作するかどうかに関するものです。この場合、「正しい」動作とは何か、または実際にテストまたは調査する実行可能プログラムの仕様はありません。

しかし、コード2を読んだところ、アカウントの現在の値を返す暗黙のAPI 要件/正確性の基準があることがわかりました。複数のスレッドがある場合、これは保証されないため、クラスはスレッドセーフではありません。getValue

関連リンク:


1 - @CKing の回答の Concurrency in Practice の引用は、定義で「無効な状態」に言及することで、「正確さ」の概念にも訴えています。ただし、メモリ モデルの JLS セクションでは、スレッド セーフが指定されていません。代わりに、彼らは「整形式の実行」について話します。

2 - この読みは、以下の OP のコメントによってサポートされています。ただし、この要件が本物であることを受け入れない場合 (たとえば、明示的に述べられていないため)、反対に、「アカウント」抽象化の動作は、Accountクラス外のコードの方法に依存するということです...これは「漏れやすい抽象化」です。

于 2015-07-10T15:11:50.650 に答える
2

これは、コンパイラがどのように順序を変更できるかについて保証がないという事実のため、純粋にスレッドセーフではありません。値は揮発性ではないため、ここに古典的な例を示します。

while(account.getValue() != 0){

}

これは次のように持ち上げることができます

while(true){
  if(account.getValue() != 0){

  } else {
      break;
  }
}

これを微妙に失敗させる可能性のあるコンパイラの楽しみの他の順列があると想像できます。ただし、getValue複数のスレッドを介してこれにアクセスすると、失敗する可能性があります。

于 2015-07-10T15:56:27.743 に答える
2

ここには、いくつかの明確な問題があります。

Q: 複数のスレッドがincrement()およびをオーバーラップして呼び出しdecrement()、その後停止した場合、またはを呼び出すスレッドがない状態で十分な時間が経過した場合、getValue() は正しい数値を返しますか?increment()decrement()

A: はい。インクリメント メソッドとデクリメント メソッドのロックにより、各インクリメント操作とデクリメント操作がアトミックに行われることが保証されます。それらは互いに干渉することはできません。


Q:十分な時間はどのくらいですか?

A: それは言いにくいです。getValue()はまったく同期せずに値にアクセスするため、Java 言語仕様は、getValue() を呼び出すスレッドが他のスレッドによって書き込まれた最新の値を参照することを保証しません。

getValue() を変更して同じロック オブジェクトをロックおよびロック解除する場合、または count を と宣言する場合volatile、時間はゼロで十分です。


Q: 呼び出しで無効な値をgetValue()返すことはできますか?

A: いいえ、初期値、完全なincrement()呼び出しの結果、または完全なdecrement()操作の結果のみを返すことができます。

しかし、その理由はロックとは関係ありません。ロックは、他のスレッドが値をインクリメントまたはデクリメントしている最中に、どのスレッドも呼び出すことを妨げません。getValue()

getValue() が完全に無効な値を返すのを防ぐのは、 value がであり、JLS は変数intの更新と読み取りが常にアトミックであることを保証することです。int

于 2015-07-10T16:06:03.350 に答える
1

短い答え:

定義上、メソッドが保護されていなくてもAccountスレッドセーフなクラスですgeValue

長い答え

実際の Java Concurrencyから、クラスは次の場合にスレッドセーフであると言われます。

スレッド セーフ クラスのインスタンスに対して順次または同時に実行される一連の操作によって、インスタンスが無効な状態になることはありません。

getValueメソッドによってクラスが常に無効な状態になることはないため、クラスAccountはスレッド セーフであると言われます。

Collections#synchronizedCollectionのドキュメントは、この感情に共鳴します。

指定されたコレクションに基づく同期された (スレッドセーフな) コレクションを返します。シリアル アクセスを保証するには、バッキング コレクションへのすべてのアクセスが、返されたコレクションを通じて行われることが重要です。ユーザーは、返されたコレクションを反復処理するときに手動で同期することが不可欠です。

 Collection c = Collections.synchronizedCollection(myCollection);
 ...   
  synchronized (c) {
  Iterator i = c.iterator(); // Must be in the synchronized block
  while (i.hasNext())
     foo(i.next());   
  }

コレクション (クラスで名前が付けられた内部クラスのオブジェクト) はスレッドセーフであるとドキュメントに記載さSynchronizedCollectionCollectionsいるにもかかわらず、コレクションを反復処理する際にコレクションを保護するようにクライアント コードに要求していることに注目してください。実際、 のiteratorメソッドは でSynchronizedCollectionはありませんsynchronized。これは、 がスレッドセーフな例と非常によく似ていますAccountが、クライアント コードは を呼び出すときに原子性を確保する必要がありますgetValue

于 2015-07-10T15:17:03.110 に答える
0

getValue を保護する必要はありません。複数のスレッドから同時にアクセスしても、悪影響はありません。オブジェクトの状態は、いつ、またはいくつのスレッドからこのメソッド ID を呼び出しても無効になることはありません (変更されないため)。

そうは言っても、このクラスを使用するスレッドセーフでないコードを書くことができます。

たとえば、次のようなもの

if (acc.getValue()>0) acc.decrement();

競合状態につながる可能性があるため、潜在的に危険です。なんで?

「0 未満に減分しない」というビジネス ルールがあり、現在の値が 1 で、このコードを実行する 2 つのスレッドがあるとします。次の順序で実行される可能性があります。

  1. スレッド 1 は、acc.getValue が >0 であることを確認します。はい!

  2. acc.getValue が >0 であるスレッド 2。はい!

  3. スレッド 1 はデクリメントを呼び出します。値は 0 です

  4. スレッド 2 呼び出しのデクリメント。値が -1 になりました

どうしたの?各機能は、ゼロを下回らないようにしましたが、協力してそれを行うことができました。これは競合状態と呼ばれます。

これを回避するには、基本的な操作を保護するのではなく、中断せずに実行する必要があるコードの一部を保護する必要があります。

したがって、このクラスはスレッドセーフですが、使用は非常に限定されています。

于 2015-07-10T15:09:54.253 に答える
0

完全にスレッドセーフです。

誰も同時にインクリメントとデクリメントvalueを行うことはできないため、誤ってカウントを失ったり獲得したりすることはありません。

getValue() 時間の経過とともに異なる値を返すという事実は、いずれにせよ発生するものです。同時性は関係ありません。

于 2015-07-10T15:03:22.837 に答える