搜尋
首頁JavaJava面試題面試官問你:你知道什麼是ABA問題嗎?

狸貓換太子

#在開始問題的闡述之前,我們先來看看一則小故事:

北宋宋真宗皇后死後,當時他的兩位愛妃劉妃和李妃都懷了孕,很顯然,誰生了兒子,誰就有可能立為正宮。劉妃久懷嫉妒之心,唯恐李妃生了兒子被立為皇后,於是與宮中總管都堂郭槐定計,在接生婆尤氏的配合下,乘李妃分娩時由於血暈而人事不知之機,將一狸貓剝去皮毛,血淋淋,光油油地換走了剛出世的太子。劉妃命宮女寇珠勒死太子,寇珠於心不忍,暗中將太子交付宦官陳琳,陳琳將太子裝在提盒中送至八賢王處撫養。再說真宗看到被剝了皮的狸貓,以為李妃產下了一個妖物,乃將其貶入冷宮。不久,劉妃臨產,生了個兒子,被立為太子,劉妃也被冊立為皇后。誰知六年後,劉後之子病夭。真宗再無子嗣,就將其皇兄八賢王之子(實為當年被換走的皇子)收為義子,並立為太子。

從這故事看出來,太子出生被換成了狸貓,最後陰差陽錯又回歸成為太子。雖然結果是一樣的,但是過程曲折了,太子真的是命途多舛啊。

為什麼要說這個故事?其實跟我們今天要介紹的問題有很大的關係。同樣的結果,可能中間不知道發生了多少次操作,那我們能認為他沒改變嗎?在不同的業務場景下,我們要仔細考慮這個問題。

ABA問題描述

#在多執行緒場景下CAS會出現 ABA問題,關於ABA問題這裡簡單科普下,例如有2個線程同時對同一個值(初始值為A)進行CAS操作,這三個線程如下:

  1. 執行緒1,期望值為A,欲更新的值為B
  2. #執行緒2,期望值為A,欲更新的值為B

線程1搶先獲得CPU時間片,而線程2因為其他原因阻塞了,線程1取值與期望的A值比較,發現相等然後將值更新為B,然後這個時候出現了線程3,期望值為B,欲更新的值為A,線程3取值與期望的值B比較,發現相等則將值更新為A,此時線程2從阻塞中恢復,並且獲得了CPU時間片,這時候線程2取值與期望的值A比較,發現相等則將值更新為B,雖然線程2也完成了操作,但是線程2並不知道值已經經過了A->B->A的變化過程。

舉個具體的例子說明

#小明在提款機,提取了50元,因為提款機問題,有兩個線程,同時把餘額從100變成50:

  • #線程1(提款機):取得目前值100,期望更新為50;
  • 執行緒2(提款機):取得目前值100,期望更新為50;
  • 執行緒1成功執行,執行緒2某種原因block了;
  • 這時,某人給小明匯款50;
  • 線程3(預設):取得目前值50,期望更新為100,這時候線程3成功執行,餘額變成100;
  • #線程2從Block恢復,取得到的也是100,compare之後,繼續更新餘額為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識別來引用,而是使用boolean變數-表示引用變數是否被更改過。 ###
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();
}
######1、初始值100,初始版本號未修改false ###2、線程t1和t2拿到一樣的初始版本號都沒有被修改false ###3、線程t1完成ABA操作,版本號碼被修改true ###4、線程t2完成CAS操作,版本號碼已經變成true,跟線程t2之前拿到的版本號false不相等,操作失敗####### #####執行結果:######
t1版本号是否被更改:false
t2版本号是否被更改:false
是否更改过:true
false 当前值:100

多说几句

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

以上是面試官問你:你知道什麼是ABA問題嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:Java学习指南。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器