高并发之CAS机制和ABA问题
什么是CAS機制
CAS是英文單詞Compare and Swap的縮寫,翻譯過來就是比較并替換
CAS機制中使用了3個基本操作數:內存地址V,舊的預期值A,要修改的新值B。
看如下幾個例子:
package com.example.demo.concurrentDemo;import org.junit.Test;import java.util.concurrent.atomic.AtomicInteger;public class CasTest {private static int count = 0;@Testpublic void test1(){for (int j = 0; j < 2; j++) {new Thread(() -> {for (int i = 0; i < 10000; i++) {count++;}}).start();}try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//結果必定 count <= 20000 System.out.println(count);}@Testpublic void test2() {for (int j = 0; j < 2; j++) {new Thread(() -> {for (int i = 0; i < 10000; i++) {synchronized (this) {count++;}}}).start();}try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//synchronized 類似于悲觀鎖//synchronized關鍵字會讓沒有得到鎖資源的線程進入BLOCKED狀態,而后在爭奪到鎖資源后恢復為RUNNABLE狀態//這個過程中涉及到操作系統用戶模式和內核模式的轉換,代價比較高 System.out.println(count);}private static AtomicInteger atoCount = new AtomicInteger(0);@Testpublic void test3() {for (int j = 0; j < 2; j++) {new Thread(() -> {for (int i = 0; i < 10000; i++) {atoCount.incrementAndGet();}}).start();}try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//Atomic操作類的底層正是用到了“CAS機制” System.out.println(atoCount);}}CAS 缺點
1) CPU開銷過大
在并發量比較高的情況下,如果許多線程反復嘗試更新某一個變量,卻又一直更新不成功,循環往復,會給CPU帶來很到的壓力。
這個可以通過看:AtomicInteger.incrementAndGet()源碼,可知這是一個無限循環,獲取實際值與預期值比較,當相等才會跳出循壞。
2) 不能保證代碼塊的原子性
CAS機制所保證的知識一個變量的原子性操作,而不能保證整個代碼塊的原子性。比如需要保證3個變量共同進行原子性的更新,就不得不使用synchronized了。
3) ABA問題
這是CAS機制最大的問題所在。
?
什么是ABA?先看下面例子:
我們先來看一個多線程的運行場景:
時間點1 :線程1查詢值是否為A?
時間點2 :線程2查詢值是否為A?
時間點3 :線程2比較并更新值為B?
時間點4 :線程2查詢值是否為B?
時間點5 :線程2比較并更新值為A?
時間點6 :線程1比較并更新值為C
在這個線程執行場景中,2個線程交替執行。線程1在時間點6的時候依然能夠正常的進行CAS操作,盡管在時間點2到時間點6期間已經發生一些意想不到的變化, 但是線程1對這些變化卻一無所知,因為對線程1來說A的確還在。通常將這類現象稱為ABA問題。
ABA發生了,但線程不知道。又或者鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化。
ABA隱患
就像兵法講的:偷梁換柱、李代桃僵?
歷史事件:趙氏孤兒
?解決ABA問題兩種方法:
1、悲觀鎖思路,加鎖;
2、樂觀鎖思路,通過AtomicStampedReference.class?
源碼實現,具體看源碼:
1. 創建一個Pair類來記錄對象引用和時間戳信息,采用int作為時間戳,實際使用的時候時間戳信息要做成自增的,否則時間戳如果重復,還會出現ABA的問題。這個Pair對象是不可變對象,所有的屬性都是final的, of方法每次返回一個新的不可變對象。
2. 使用一個volatile類型的引用指向當前的Pair對象,一旦volatile引用發生變化,變化對所有線程可見。
3. set方法時,當要設置的對象和當前Pair對象不一樣時,新建一個不可變的Pair對象。
4. compareAndSet方法中,只有期望對象的引用和版本號和目標對象的引用和版本好都一樣時,才會新建一個Pair對象,然后用新建的Pair對象和原理的Pair對象做CAS操作。
5.?實際的CAS操作比較的是當前的pair對象和新建的pair對象,pair對象封裝了引用和時間戳信息。
Demo:
?
@Testpublic void test4() {final int timeStamp = atoReferenceCount.getStamp();new Thread(() -> {while(true){if(atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),atoReferenceCount.getReference()+1, timeStamp, timeStamp + 1)){System.out.println("11111111");break;}}},"線程1:").start();new Thread(() -> {while(true){if(atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),atoReferenceCount.getReference()+1, timeStamp, timeStamp + 1)){System.out.println("2222222");break;}}},"線程2:").start();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atoReferenceCount.getReference());}第二個沒有執行,因為時間戳不對了。
修改下代碼:
@Testpublic void test4() {for (int i = 0; i < 10; i++) {new Thread(() -> {boolean f = atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),atoReferenceCount.getReference() + 1, atoReferenceCount.getStamp(),atoReferenceCount.getStamp() + 1);System.out.println("線程"+Thread.currentThread()+"result="+f);}, "線程:"+i).start();}try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atoReferenceCount.getReference());}結果:可見線程:0,比較的時候發現時間戳變了,所以沒有+1。
demo2:
@Testpublic void test5() {for (int i = 0; i < 4; i++) {new Thread(() -> {for (int j = 0; j < 500; j++) {boolean f = atoReferenceCount.compareAndSet(atoReferenceCount.getReference(),atoReferenceCount.getReference() + 1, atoReferenceCount.getStamp(),atoReferenceCount.getStamp() + 1);System.out.println("線程"+Thread.currentThread()+">>j="+j+",result="+f);}}, "線程:"+i).start();}try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atoReferenceCount.getReference());}?有3次比較時間戳發現已經不同
?
參考:
https://blog.csdn.net/qq_32998153/article/details/79529704
轉載于:https://www.cnblogs.com/xiaozhuanfeng/p/10846180.html
總結
以上是生活随笔為你收集整理的高并发之CAS机制和ABA问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sass 插值语句的使用
- 下一篇: 第十一周编程作业