8

整数値を設定または取得するために使用できる単純なスレッド セーフなクラスを作成したいと考えています。

最も簡単な方法は、synchronizedキーワードを使用することです。

public class MyIntegerHolder {

    private Integer value;

    synchronized public Integer getValue() {
        return value;
    }

    synchronized public void setValue(Integer value) {
        this.value = value;
    }

}

volatileを使用することもできます:

public class MyIntegerHolder {

    private volatile Integer value;

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

}

volatileキーワードを持つクラスはスレッドセーフですか?

次の一連のイベントを検討してください。

  1. スレッド A は値を 5 に設定します。
  2. スレッド B は値を 7 に設定します。
  3. スレッド C が値を読み取ります。

Java 言語仕様から、

  • "1"は"3"の前に発生します
  • "2"は"3"の前に発生します

しかし、「1」が「2」の前に発生するという仕様からどのように続くかわかりません。そのため、「1」は「2」の前に発生しない と思われます。

スレッド C は 7 または 5 を読み取る可能性があると思います。volatileキーワードを持つクラスはスレッドセーフではなく、次のシーケンスも可能だと思います。

  1. スレッド A は値を 5 に設定します。
  2. スレッド B は値を 7 に設定します。
  3. スレッド C は 7 を読み取ります。
  4. スレッド D は 5 を読み取ります。
  5. スレッド C は 7 を読み取ります。
  6. スレッド D は 5 を読み取ります。
  7. ...

MyIntegerHolder with volatileはスレッドセーフではないと仮定するのは正しいですか?

AtomicInteger を使用してスレッドセーフな整数ホルダーを作成することは可能ですか?

public class MyIntegerHolder {

    private AtomicInteger atomicInteger = new AtomicInteger();

    public Integer getValue() {
        return atomicInteger.get();
    }

    public void setValue(Integer value) {
        atomicInteger.set(value);
    }

}

?

以下は、『Java Concurrency In Practice』の一部です。

"アトミック変数の読み取りと書き込みは、揮発性変数と同じメモリ セマンティクスを持ちます。 "

スレッドセーフなMyIntegerHolderを記述する最良の (できればノンブロッキングの) 方法は何ですか?

答えを知っているなら、それが正しいと思う理由を知りたいです。仕様通りですか?もしそうなら、どのように?

4

5 に答える 5

4

キーワードsynchronizedは、Thread A and Thread Bにアクセスしたい場合Integer、同時にアクセスできないことを示しています。A は B に、私がそれをやり終えるまで待つように言っています。

一方、volatileスレッドをより「フレンドリー」にします。彼らはお互いに話し始め、協力してタスクを実行します。したがって、B がアクセスしようとすると、A はその時点までに行ったすべてのことを B に通知します。B は変更を認識し、A が去ったところから仕事を続けることができます。

Java では、Atomicこの理由により、隠れてvolatileキーワードを使用しているため、ほとんど同じことを行っていますが、時間と労力を節約できます。

あなたが探しているのはAtomicInteger、あなたはこれについて正しいです。実行しようとしている操作では、これが最良の選択です。

There are two main uses of `AtomicInteger`:

 * As an atomic counter (incrementAndGet(), etc) that can be used by many threads concurrently

 * As a primitive that supports compare-and-swap instruction (compareAndSet()) to implement non-blocking algorithms. 

一般的な注意事項で質問に答えるには

必要なものによって異なります。私はそれsynchronizedが間違っていvolatileて良いと言っているのではありませんsynchronized。絶対的な答えはありません。具体的なケースや使用シナリオがたくさんあります。

私のブックマークのいくつか:

同時実行のヒント

コア Java 並行性

Java 並行性

アップデート

ここで入手可能な Java Concurrency 仕様から:

パッケージ java.util.concurrent.atomic

単一変数でのロックフリー スレッドセーフ プログラミングをサポートするクラスの小さなツールキット。

Instances of classes `AtomicBoolean`, `AtomicInteger`, `AtomicLong`, and `AtomicReference` each provide access and updates to a single variable of the corresponding type.
Each class also provides appropriate utility methods for that type.
For example, classes `AtomicLong` and AtomicInteger provide atomic increment methods.

The memory effects for accesses and updates of atomics generally follow the rules for volatiles:

get has the memory effects of reading a volatile variable.
set has the memory effects of writing (assigning) a volatile variable.

またこちらから

Java プログラミング言語のvolatileキーワード:

(Java のすべてのバージョンで) volatile 変数への読み取りと書き込みにはグローバルな順序があります。これは、揮発性フィールドにアクセスするすべてのスレッドが、(潜在的に) キャッシュされた値を使用する代わりに、続行する前に現在の値を読み取ることを意味します。(ただし、揮発性の読み取りと書き込みと通常の読み取りと書き込みの相対的な順序についての保証はありません。つまり、一般的に有用なスレッド構造ではありません。)

于 2013-05-03T12:15:07.263 に答える
1

変数の取得/設定のみが必要な場合は、変数を揮発性として宣言するだけで十分です。AtomicInteger がどのように設定/取得されるかを確認すると、同じ実装が表示されます

private volatile int value;
...

public final int get() {
    return value;
}

public final void set(int newValue) {
    value = newValue;
}

しかし、揮発性フィールドをこのようにアトミックにインクリメントすることはできません。ここで、AtomicInteger.incrementAndGet または getAndIncrement メソッドを使用します。

于 2013-05-03T12:14:30.547 に答える
0

volatile を使用した MyIntegerHolder はスレッドセーフです。ただし、並列プログラムを実行している場合は、多くのアトミック操作も提供するため、AtomicInteger を使用することをお勧めします。

次の一連のイベントを検討してください。

  1. スレッド A は値を 5 に設定します。
  2. スレッド B は値を 7 に設定します。
  3. スレッド C が値を読み取ります。

Java 言語仕様から、

  • "1" は "3" の前に発生します
  • "2" は "3" の前に発生します

しかし、「1」が「2」の前に発生するという仕様からどのように続くかわかりません。そのため、「1」は「2」の前に発生しないと思われます。

スレッド C が 7 または 5 を読み取る可能性があると思われます。volatile キーワードを持つクラスはスレッドセーフではないと思います

「1」は「3」の前に発生し、「2」は「3」の前に発生します。「1」は「2」の前に発生しませんが、スレッドセーフではないという意味ではありません。問題は、あなたが提供した例があいまいであるということです。「値を 5 に設定する」、「値を 7 に設定する」、「値を読み取る」と言っている場合、これらは順番に行われ、常に 7 の値を読み取ることができます。ただし、3 つのスレッドがシーケンスなしで同時に実行されると言っている場合は、「値の読み取り」が最初に発生する可能性があるため、0 の値を取得することもできます。しかし、これはスレッドセーフでは何もありません.3つのアクションから期待される順序はありません.

于 2016-11-20T19:07:40.760 に答える
0

Java 言語仕様の第 17 章では、共有変数の読み取りや書き込みなどのメモリ操作に関する先行発生関係が定義されています。あるスレッドによる書き込みの結果は、書き込み操作が読み取り操作の前に発生した場合にのみ、別のスレッドによる読み取りに表示されることが保証されます。

  1. synchronized および volatile コンストラクト、および Thread.start() および Thread.join() メソッドは、事前発生関係を形成できます。特に、スレッド内の各アクションは、プログラムの順序で後で来るそのスレッド内のすべてのアクションの前に発生します。
  2. モニターのロック解除 (同期されたブロックまたはメソッドの終了) は、その同じモニターの後続のすべてのロック (同期されたブロックまたはメソッドのエントリ) の前に発生します。また、先行発生関係は推移的であるため、ロックを解除する前のスレッドのすべてのアクションは、そのモニターをロックしているスレッドに続くすべてのアクションの前に発生します。
  3. 揮発性フィールドへの書き込みは、同じフィールドの後続のすべての読み取りの前に発生します。揮発性フィールドの書き込みと読み取りには、モニターの開始と終了と同様のメモリ整合性効果がありますが、相互排他ロックは必要ありません。
  4. スレッドでの start の呼び出しは、開始されたスレッドのアクションの前に発生します。
  5. スレッド内のすべてのアクションは、他のスレッドがそのスレッドの結合から正常に戻る前に発生します。

参照: http://developer.android.com/reference/java/util/concurrent/package-summary.html

私の理解から3の意味:あなたが書いた場合(読み取り結果に基づいていない)/読み取りは問題ありません。書き込む場合(インクリメントなどの読み取り結果に基づく)/読み取りは問題ありません。volatile であるため、「相互排他ロックを伴わない」

于 2015-04-01T00:54:47.793 に答える
-1

この質問は私にとって簡単ではありませんでした。なぜなら、私は (誤って)先行発生関係についてすべてを知っていれば、Java メモリ モデルとvolatileのセマンティクスを完全に理解できると思っていたからです。

「JSR-133: JavaTM Memory Model and Thread Specification」というドキュメントで最も適切な説明を見つけました 。

上記のドキュメントの最も関連する部分は、セクション「7.3 整形式の実行」です。

Java メモリ モデルは、プログラムのすべての実行が整形式であることを保証します。実行は、次の場合にのみ整形式です。

  • 発生前の一貫性に従う
  • 同期順序の一貫性に従います
  • ... (他のいくつかの条件も真でなければなりません)

プログラムの動作について結論を出すには、一貫性が発生する前に通常は十分ですが、この場合はそうではありません。揮発性書き込みが別の揮発性書き込みの前に発生しないためです。

volatileを持つ MyIntegerHolderはスレッドセーフですが、その安全性は同期順序の一貫性に由来します。

私の意見では、スレッド B が値を 7 に設定しようとしているとき、A はその瞬間までに行ったすべてのことを B に通知しません (他の回答の 1 つが示唆したように) - volatile 変数の値についてのみ B に通知します. スレッド A は、スレッド B が実行したアクションが読み取りであり、書き込みではない場合、B にすべてのことを通知します (他の変数に値を割り当てます) (この場合、これら 2 つのスレッドが実行するアクションの間には先行発生の関係が存在します)。

于 2013-05-03T19:15:06.570 に答える