volatile
は可視性を許可し、AtomicInteger
は原子性を許可することを知っています。では、揮発性のAtomicInteger
を使用すると、同期メカニズムをこれ以上使用する必要がないということですか?
例えば。
class A {
private volatile AtomicInteger count;
void someMethod(){
// do something
if(count.get() < 10) {
count.incrementAndGet();
}
}
これはスレッドセーフですか?
_Atomic*
_が実際にbothの原子性とボラティリティを与えると私は信じています。したがって、(たとえば)AtomicInteger.get()
を呼び出すと、必ずlatest値が取得されます。これは_Java.util.concurrent.atomic
_ パッケージドキュメント に文書化されています:
アトミックのアクセスと更新のメモリ効果は、Java™言語仕様のセクション17.4に記載されているように、揮発性の規則に従います。
- getには、揮発性変数を読み取るメモリ効果があります。
- setには、揮発性変数の書き込み(割り当て)によるメモリー効果があります。
- lazySetには、揮発性変数の書き込み(割り当て)によるメモリ効果があります。ただし、通常の不揮発性書き込みでは順序変更の制約を課さない後続(ただし前ではない)のメモリアクションによる順序変更が可能です。他の使用法のコンテキストの中で、>-ガベージコレクションのために、再度アクセスされることのない参照をnullにするときに、lazySetが適用される場合があります。
- weakCompareAndSetは、アトミックに変数を読み取り、条件付きで書き込みますが、発生前の順序付けは作成しません。そのため、weakCompareAndSetのターゲット以外の変数の前または後の読み取りおよび書き込みに関しては保証されません。
- compareAndSetおよびgetAndIncrementなどの他のすべての読み取りおよび更新操作には、揮発性変数の読み取りと書き込みの両方のメモリ効果があります。
今あなたが持っているなら
_volatile AtomicInteger count;
_
volatile
の部分は、各スレッドが最新のAtomicInteger
参照を使用することを意味し、AtomicInteger
であるという事実は、あなたがまたそのオブジェクトの最新の値を参照してください。
これが必要になることは一般的ではありません(IME)-通常、別のオブジェクトを参照するためにcount
を再割り当てしないためです。代わりに、次のようになります。
_private final AtomicInteger count = new AtomicInteger();
_
その時点で、それがfinal
変数であるということは、すべてのスレッドが同じオブジェクトを処理することを意味し、_Atomic*
_オブジェクトであるという事実は、その中で最新の値が表示されることを意味しますオブジェクト。
シングルスレッドモードとマルチスレッドモードで同じ結果が得られるとスレッドセーフを定義した場合、それはスレッドセーフではありません。シングルスレッドモードでは、カウントは10を超えることはありませんが、マルチスレッドモードでは可能です。
問題は、get
とincrementAndGet
はアトミックですが、if
はアトミックではありません。非アトミック操作はいつでも一時停止できることに注意してください。例えば:
count = 9
_。if(count.get() <10)
を実行してtrue
を取得し、そこで停止しました。if(count.get() <10)
を実行してtrue
も取得するため、count.incrementAndGet()
を実行して終了します。今_count = 10
_です。count.incrementAndGet()
を実行しますが、シングルスレッドモードでは決して発生しない_count = 11
_になりました。より遅いsynchronized
を使用せずにスレッドセーフにしたい場合は、代わりに次の実装を試してください。
_class A{
final AtomicInteger count;
void someMethod(){
// do something
if(count.getAndIncrement() <10){
// safe now
} else count.getAndDecrement(); // rollback so this thread did nothing to count
}
_
答えはこのコードにあります
これはAtomicIntegerのソースコードです。値は揮発性です。したがって、AtomicIntegerは内部でVolatileを使用します。
元のセマンティクスを維持し、複数のスレッドをサポートするには、次のようにします。
public class A {
private AtomicInteger count = new AtomicInteger(0);
public void someMethod() {
int i = count.get();
while (i < 10 && !count.compareAndSet(i, i + 1)) {
i = count.get();
}
}
}
これにより、カウントが10に達するスレッドが回避されます。
クエリには2つの質問があるため、クエリは2つの部分で回答できます。
1)アトミック変数に関するOracleのチュートリアルドキュメントを参照: https://docs.Oracle.com/javase/tutorial/essential/concurrency/atomicvars.html
Java.util.concurrent.atomicパッケージは、単一の変数に対するアトミック操作をサポートするクラスを定義します。すべてのクラスには、揮発性変数の読み取りと書き込みのように機能するgetメソッドとsetメソッドがあります。つまり、セットには、同じ変数に対する後続のgetとの「前に発生」の関係があります。アトミックcompareAndSetメソッドには、整数アトミック変数に適用される単純なアトミック算術メソッドと同様に、これらのメモリ整合性機能もあります。
したがって、ここで他の回答が述べているように、アトミック整数は内部で揮発性を使用します。したがって、アトミック整数を揮発性にしても意味がありません。メソッドを同期する必要があります。
UdemyでJohn Purcellの無料ビデオをご覧ください。複数のスレッドがvolatileキーワードを変更しようとすると、volatileキーワードの失敗が表示されます。シンプルで美しい例。 https://www.udemy.com/course/Java-multithreading/learn/lecture/108950#overview
Johnの例の揮発性カウンターをアトミック変数に変更すると、彼のコードは、彼のチュートリアルで行ったようにsunchronizedキーワードを使用しなくても成功することが保証されています
2)コードにアクセスする:スレッド1がアクションを開始し、「someMethod」がgetを実行してサイズをチェックするとします。 getAndIncrementが(たとえば、スレッド1によって)実行される前に、別のスレッド(たとえば、スレッド2)がキックインしてカウントを10に増やし、それが終了する可能性があります。その後、スレッド1が再開され、カウントが11に増加します。これは誤った出力です。これは、「someMethod」が同期の問題から保護されていないためです。キーワードvolatileの理解を深めるために、john purcellのビデオを見てvolatileが失敗する場所を確認することをお勧めします。彼の例では、これをatomicintegerに置き換えて、魔法を見てください。