問題の詳細を説明する前に、短い物語を見てみましょう:
北宋の宋真宗皇后の死後、彼の最愛の側室である劉皇后と李皇后はともに妊娠しており、息子を産んだ方が正宮になる可能性が高いのは明らかでした。劉妃は長年嫉妬しており、李妃が男の子を産んで王妃にされるのではないかと恐れ、郭淮、宮司の杜唐、そして産婆のヨウシの協力を得て計画を立てた。出産時に昏睡状態で亡くなったが、なんとジャコウネコの毛皮が剥げてしまい、血まみれでテカテカになって、生まれたばかりの王子を連れ去ってしまったのだ。劉妃は宮廷侍女コウ・朱に王子を絞め殺すように命じたが、コウ・朱はそれに耐えられず、密かに宦官の陳林に引き渡し、陳林は王子をスーツケースに入れて八賢王のもとへ送った。彼を育てるために。さらに、皮を剥いだジャコウネコを見た真宗は、李妃が化け物を産んだのではないかと思い、李妃を寒宮に降格させた。やがて、劉妃が陣痛を迎えて男の子を出産したため、その子が王子となり、劉妃も王妃に任命された。予期せぬことに、6年後、劉女王の息子は病気で亡くなりました。鎮宗には後継者がいなかったため、兄の王八賢(実際にはその年に更迭された王子)の子を養子として迎え、皇太子に据えた。
この物語からわかるのは、王子は生まれたときにジャコウネコに置き換えられ、その後、奇妙なことが重なって最終的に王子に戻ったということです。結果は同じでも、その過程は紆余曲折あって、王子は本当に運命が悪い。
なぜこの話をするのか?実は、これは今日紹介する問題と大きく関係しています。同じ結果ですが、途中で何回操作が発生したか分かりませんが、変化がないと考えて良いでしょうか?さまざまなビジネス シナリオにおいて、この問題を慎重に検討する必要があります。
CAS が表示されます
ABA 問題, ここに ABA 問題についての簡単な科学があります。たとえば、同じ値 (初期値は A) に対して CAS 操作を同時に実行する 2 つのスレッドがあります。3 つのスレッドは次のとおりです:
スレッド 1 が最初に CPU タイム スライスを取得しますが、スレッド 2 は他の理由でブロックされています。スレッド 1 はその値を A の期待値と比較し、等しいことがわかり、値を B に更新します。すると、この時点でスレッド 3 が表示されます。期待値は B で、更新される値は A です。スレッド 3 は、値を期待値 B と比較します。値が等しいことが判明した場合、このとき、スレッド 2 はブロッキングから回復し、CPU タイム スライスを取得します。この値と期待値 A を比較し、等しい場合、値を B に更新します。スレッド 2 も操作を完了しましたが、スレッド 2 は値が A->B->A に変更されたことを知りません。
シャオミンは現金自動預け払い機の問題のため、現金自動預け払い機から50元を引き出しました。 2 つのスレッドがあり、同時に残高を 100 から 50 に変更します。
この時点では、実際の残高は 100 (100-50 50)
であるはずですが、実際には 50 (100-50) になっていることがわかります。 50 -50)
これは、間違った提出結果をもたらす ABA の問題です。
ABAの問題を解決するには、バージョン番号を追加できます。 V は変更後、バージョン番号が 1
ずつ増加します。AtomicStampedReference は、オブジェクトの値とバージョン番号を内部的に維持します。AtomicStampedReference オブジェクトを作成するときは、初期値と初期バージョン番号を渡す必要があります。
AtomicStampedReference がオブジェクト値を設定する場合、書き込みが成功するには、オブジェクト値とステータス スタンプの両方が期待値を満たしている必要があります。
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1); public static void main(String[] args) { //第一个线程 new Thread(() -> { System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp()); //睡眠1秒,是为了让t2线程也拿到同样的初始版本号 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); },"t1").start(); // 第二个线程 new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println("t2拿到的初始版本号:" + stamp); //睡眠3秒,是为了让t1线程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最新版本号:" + atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t当前值:" + atomicStampedReference.getReference()); },"t2").start(); }
1. 初期値 100、初期バージョン番号 1
2. スレッド t1 と t2 は同じ初期バージョン番号
3 を取得します。 t1 は ABA 操作を完了し、バージョン番号は 3 に増加しました
4. スレッド t2 は CAS 操作を完了し、最新のバージョン番号は 3 になりました。これは、スレッド t2 が以前に取得したバージョン番号 1 と等しくありません。操作は失敗しました
実行結果:#
t1拿到的初始版本号:1 t2拿到的初始版本号:1 最新版本号:3 false 当前值:100
バージョン番号をリファレンスに追加し、A -> B -> C -> D などのリファレンスの変更プロセス全体を追跡できます。 -> A. AtomicStampedReference を通じて、プロセス中に 3 回変更された参照変数を知ることができます。ただし、場合によっては、参照変数が何回変更されたかではなく、単に変更されたかどうかだけを気にすることがあります。したがって、AtomicMarkableReference
があります。
AtomicMarkableReference の唯一の違いは、参照を識別するために int を使用しなくなり、参照変数が変更されたかどうかを示すためにブール変数を使用することです。 <pre class="brush:php;toolbar:false;">private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false);
public static void main(String[] args) {
// 第一个线程
new Thread(() -> {
System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked());
//睡眠1秒,是为了让t2线程也拿到同样的初始版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true);
atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true);
},"t1").start();
// 第二个线程
new Thread(() -> {
boolean isMarked = atomicMarkableReference.isMarked();
System.out.println("t2版本号是否被更改:" + isMarked);
//睡眠3秒,是为了让t1线程完成ABA操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("是否更改过:" + atomicMarkableReference.isMarked());
System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "\t当前值:" + atomicMarkableReference.getReference());
},"t2").start();
}</pre>
2. スレッド t1 と t2 は同じ初期バージョン番号を持ち、変更されていません false結果:3. スレッド t1 は ABA 操作を完了し、バージョン番号は true に変更されました
4. スレッド t2 は CAS 操作を完了し、バージョン番号は true に変更されましたが、これは取得したバージョン番号 false と等しくありません以前のスレッド t2 によって操作が失敗しました
以上是本期关于CAS领域的一个经典ABA问题的解析,不知道你在实际的工作中有没有遇到过,但是在面试中这块是并发知识考查的重点。如果你还没接触过此类的问题,我的建议是你自己将上面的代码运行一下,结合理论去理解一下ABA问题所带来的问题以及如何解决他,这对你日后的开发工作也是有莫大的帮助的!t1版本号是否被更改:false
t2版本号是否被更改:false
是否更改过:true
false 当前值:100
多说几句
以上が面接官は「ABA 問題とは何か知っていますか?」と尋ねます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。