ホームページ  >  記事  >  Java  >  面接官は「ABA 問題とは何か知っていますか?」と尋ねます。

面接官は「ABA 問題とは何か知っていますか?」と尋ねます。

Java学习指南
Java学习指南転載
2023-07-26 15:09:451180ブラウズ

王子のためのジャコウネコ

問題の詳細を説明する前に、短い物語を見てみましょう:

北宋の宋真宗皇后の死後、彼の最愛の側室である劉皇后と李皇后はともに妊娠しており、息子を産んだ方が正宮になる可能性が高いのは明らかでした。劉妃は長年嫉妬しており、李妃が男の子を産んで王妃にされるのではないかと恐れ、郭淮、宮司の杜唐、そして産婆のヨウシの協力を得て計画を立てた。出産時に昏睡状態で亡くなったが、なんとジャコウネコの毛皮が剥げてしまい、血まみれでテカテカになって、生まれたばかりの王子を連れ去ってしまったのだ。劉妃は宮廷侍女コウ・朱に王子を絞め殺すように命じたが、コウ・朱はそれに耐えられず、密かに宦官の陳林に引き渡し、陳林は王子をスーツケースに入れて八賢王のもとへ送った。彼を育てるために。さらに、皮を剥いだジャコウネコを見た真宗は、李妃が化け物を産んだのではないかと思い、李妃を寒宮に降格させた。やがて、劉妃が陣痛を迎えて男の子を出産したため、その子が王子となり、劉妃も王妃に任命された。予期せぬことに、6年後、劉女王の息子は病気で亡くなりました。鎮宗には後継者がいなかったため、兄の王八賢(実際にはその年に更迭された王子)の子を養子として迎え、皇太子に据えた。

この物語からわかるのは、王子は生まれたときにジャコウネコに置き換えられ、その後、奇妙なことが重なって最終的に王子に戻ったということです。結果は同じでも、その過程は紆余曲折あって、王子は本当に運命が悪い。

なぜこの話をするのか?実は、これは今日紹介する問題と大きく関係しています。同じ結果ですが、途中で何回操作が発生したか分かりませんが、変化がないと考えて良いでしょうか?さまざまなビジネス シナリオにおいて、この問題を慎重に検討する必要があります。

#ABA 問題の説明

マルチスレッド シナリオでは、

CAS が表示されます ABA 問題, ここに ABA 問題についての簡単な科学があります。たとえば、同じ値 (初期値は A) に対して CAS 操作を同時に実行する 2 つのスレッドがあります。3 つのスレッドは次のとおりです:

  1. スレッド 1、期待値は A、更新される値は B
  2. スレッド 2、期待値は A、更新される値is B

スレッド 1 が最初に CPU タイム スライスを取得しますが、スレッド 2 は他の理由でブロックされています。スレッド 1 はその値を A の期待値と比較し、等しいことがわかり、値を B に更新します。すると、この時点でスレッド 3 が表示されます。期待値は B で、更新される値は A です。スレッド 3 は、値を期待値 B と比較します。値が等しいことが判明した場合、このとき、スレッド 2 はブロッキングから回復し、CPU タイム スライスを取得します。この値と期待値 A を比較し、等しい場合、値を B に更新します。スレッド 2 も操作を完了しましたが、スレッド 2 は値が A->B->A に変更されたことを知りません。

具体的な例を挙げてください

シャオミンは現金自動預け払い機の問題のため、現金自動預け払い機から50元を引き出しました。 2 つのスレッドがあり、同時に残高を 100 から 50 に変更します。

  • スレッド 1 (現金自動預け払い機): 現在の値 100 を取得し、それを 50 に更新することを期待します。 ;
  • スレッド 2 (現金自動預け払い機): 現在値 100 を取得、50 に更新されることが予想されます;
  • スレッド 1 は正常に実行されました、スレッド 2 は何らかの理由でブロックされています ;
  • この時点で、誰かが Xiao Ming に 50 を送金します;
  • スレッド 3 (デフォルト):現在の値 50 を取得し、それを 100 に更新することを期待します。この時点で、スレッド 3 が正常に実行され、残高は 100 になります。
  • スレッド 2 はブロックから回復し、次の値を取得します。 100。比較後、残高を 50 に更新し続けます。

この時点では、実際の残高は 100 (100-50 50) であるはずですが、実際には 50 (100-50) になっていることがわかります。 50 -50)これは、間違った提出結果をもたらす ABA の問題です。

解決策

ABAの問題を解決するには、バージョン番号を追加できます。 V は変更後、バージョン番号が 1

ずつ増加します。

コード例

AtomicStampedReference による ABA の問題の解決

  • 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

#AtomicMarkableReference を通じて ABA 問題を解決する

AtomicStampedReference

バージョン番号をリファレンスに追加し、A -> B -> C -> D などのリファレンスの変更プロセス全体を追跡できます。 -> A. AtomicStampedReference を通じて、プロセス中に 3 回変更された参照変数を知ることができます。ただし、場合によっては、参照変数が何回変更されたかではなく、単に変更されたかどうかだけを気にすることがあります。したがって、AtomicMarkableReference があります。 AtomicMarkableReference の唯一の違いは、参照を識別するために int を使用しなくなり、参照変数が変更されたかどうかを示すためにブール変数を使用することです。 <pre class="brush:php;toolbar:false;">private static AtomicMarkableReference&lt;Integer&gt; atomicMarkableReference = new AtomicMarkableReference&lt;Integer&gt;(100,false); public static void main(String[] args) { // 第一个线程 new Thread(() -&gt; { System.out.println(&quot;t1版本号是否被更改:&quot; + 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); },&quot;t1&quot;).start(); // 第二个线程 new Thread(() -&gt; { boolean isMarked = atomicMarkableReference.isMarked(); System.out.println(&quot;t2版本号是否被更改:&quot; + isMarked); //睡眠3秒,是为了让t1线程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(&quot;是否更改过:&quot; + atomicMarkableReference.isMarked()); System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + &quot;\t当前值:&quot; + atomicMarkableReference.getReference()); },&quot;t2&quot;).start(); }</pre>

1. 初期値は 100 で、初期バージョン番号は変更されていません false
2. スレッド t1 と t2 は同じ初期バージョン番号を持ち、変更されていません false

3. スレッド t1 は ABA 操作を完了し、バージョン番号は true に変更されました
4. スレッド t2 は CAS 操作を完了し、バージョン番号は true に変更されましたが、これは取得したバージョン番号 false と等しくありません以前のスレッド t2 によって操作が失敗しました

結果:

t1版本号是否被更改:false
t2版本号是否被更改:false
是否更改过:true
false 当前值:100

多说几句

以上是本期关于CAS领域的一个经典ABA问题的解析,不知道你在实际的工作中有没有遇到过,但是在面试中这块是并发知识考查的重点。如果你还没接触过此类的问题,我的建议是你自己将上面的代码运行一下,结合理论去理解一下ABA问题所带来的问题以及如何解决他,这对你日后的开发工作也是有莫大的帮助的!

以上が面接官は「ABA 問題とは何か知っていますか?」と尋ねます。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はJava学习指南で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。