目錄
1. CAS 的執行流程
2. CAS 中的 ABA 問題
3. 如何解決 CAS 中的 ABA 問題
4.CAS 在Java 中的實現類有哪些
1. CAS 的執行流程
CAS 比較并替換的大致流程是這樣的:
- 它有三個操作單位:V(內存值),A(預期的舊值),B(新值),
- 比較 V 的值和 A 的值是否相等,
- 如果相等的話,則將 V 的值替換成 B,否則就提示用戶修改失敗。
正常情況(沒有其他線程修改內存值):
?特殊情況(有其他線程干擾修改值):
2. CAS 中的 ABA 問題
????????什么是 ABA 問題呢 ? 對于上圖中的有其他線程修改內存值的情況,當線程 1 在執行 CAS 之前,如果有兩個線程過來修改內存中的值了:線程 2 將原來的值給修改了,線程 3 又把內存的值給改回來了,這個時候線程 1 再去執行 CAS 就會出問題,這個就是 ABA 問題。
【舉個例子
比如說張三原來賬上有 200 元,此時張三需要給李四轉賬 100 元,如果在轉賬的時候不小心點了兩次提交,那么此時就會執行兩次 CAS。
① 正常情況下,沒有其他人給我轉賬的時候 >>
第一次 CAS :
- A(舊值):200
- B(新值):100
- V(內存值):200
- 比較 V 和 A 的值
- 相等,張三 - 100 元,李四?+ 100 元
- 扣款成功(張三余額 100 元)
第二次 CAS:
- A(舊值):200
- B(新值):100
- V(內存值):100
- 比較 V 和 A 的值
- 不相等,不能進行扣款操作
- 扣款失敗(張三余額 100 元)
② ABA 情況,轉賬途中有第三方介入 >>
第一次 CAS :
- A(舊值):200
- B(新值):100
- V(內存值):200
- 比較 V 和 A 的值
- 相等,張三 - 100 元,李四?+ 100 元
- 扣款成功(張三余額 100 元)
在第二次 CAS 之前,此時張三的哥們王五剛好還了張三 100 元,此時 V(內存值)被改為了 200,也就是說張三此時賬上的余額又變回了?200。
第二次 CAS:
- A(舊值):200
- B(新值):100
- V(內存值):200
- 比較 V 和 A 的值
- 相等,張三 - 100 元,李四?+ 100 元
- 扣款成功(張三余額 100 元)
上述有第三方介入的情況,原本張三在轉賬 100 元之后,賬戶上應該還剩 20 元的,最終卻只剩 100 元了,這就是 CAS 的 ABA 問題。
3. 如何解決 CAS 中的 ABA 問題
常見的辦法就是引入版本號(version)!!
還是上述轉賬的例子,只不過這次給每個線程的操作都加上新的版本號:
① 線程一(張三):獲取原內存值 200_version_1 ,比較 V 和 A,相等,扣款成功,然后將內存值修改為 100_version_2。
② 線程三(王五):獲取原內存值 100_version_2,比較 V 和 A,相等,轉賬成功,然后將內存值修改為 200_version_3。
③ 線程二(張三):獲取原內存值 200_version_3,比較 V 和 A(200_version_1),不相等,扣款失敗,退出修改。
此時張三賬戶上余額為 200,這才是合理的。
4.CAS 在Java 中的實現類有哪些
Java 中提供的 AtomicXXX 類,都是 CAS 的具體實現。例如:AtomicInteger,AtomicLong 等等。
AtomicInteger 的底層源碼:
【面試問題】
① 此時面試官可能會問:Java 提供的 AtomicInteger 是否存在 ABA 問題 ??
答案是存在!!因為 AtomicInteger 沒有引入版本號。
② 面試官接著問:那如何解決 AtomicInteger 的 ABA 問題 ??
使用 Atomic 家族的 AtomicStampedReference 類,它在使用的時候,必須指定一個版本號,并且它的對象在調用?CAS 的方法時,它需要設置 4 個參數,分別是:
- 舊值 A
- 新值 B
- 當前版本
- 執行修改后的版本號
所以使用這個類可以解決 ABA 問題。
AtomicStampedReference<Integer> as = new AtomicStampedReference<>(100,1);
as.compareAndSet(200,100,1,2); // 舊值,新值,當前版本號,修改后的版本號