多線程CAS

CAS只是一種思想,就是比較並替換。它有三個操作數:內存值V舊的預期值A要修改的新值B。只有當預期值A和內存值V相同的時候,纔將內存值V修改爲新值B,並返回ture。否則什麼都不做,返回false。CAS一定要與volatile變量配合使用,這樣才能保證線程每次拿到的變量都是主內存中最新的那個值,否則,舊的預期值A對某個線程來說,可能永遠是一個不會變的值A。

實現:Actomic原子類就是使用的CAS機制。例如i++操作,假如i的初始值爲0,線程1來嘗試修改i的值,從0修改到1。首先線程1會用舊的預期值0內存值i(此時也等於0)作比較,比較結果一致,修改i的值爲新值1,i的值變爲1。此時又來了一個線程2也嘗試修改i的值,從0修改到1,首先線程2也會用舊的預期值0內存值i(此時也等於1)作比較,比較結果不一致,什麼都不做,直接返回false,表示修改失敗。

優點:CAS操作是抱着樂觀態度進行的,它總是認爲自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,併成功更新,其餘均會失敗。失敗的線程不會被掛起,僅是被告知失敗,並允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的干擾,並進行恰當的處理。

缺點:CAS存在ABA問題,比如一個人賬戶裏有100塊錢,他想取50但是銀行出現錯誤,同時執行了兩次減少50的操作,但他自己只發出了一次指令。當第一次執行的時候採用CAS機制取出50塊錢,但他的媽媽從別的操作系統給他又轉了50塊,這樣賬戶裏的錢又是100塊,那麼採用CAS機制就會繼續執行扣款操作,這樣就是ABA問題,丟了50塊錢。

解決方案:ABA問題可以對每個值添加一個版本號來判斷。

 

CAS優化:

比如AtomicInteger是通過無限不停地循環去修改value,直到修改成功爲止。但是在併發數非常高的情況下,可能有很多線程會不停的自旋,進入一個無限重複的循環中。這些線程不停地獲取值,然後發起CAS操作,但是發現這個值被別人改過了,於是再次進入下一個循環,獲取值,發起CAS操作又失敗了,再次進入下一個循環。導致大量線程空循環,自旋轉,性能和效率都不是特別好。

於是在JDK8之後出現了LongAdder,其父類是Striped64,它就是嘗試使用分段CAS以及自動分段遷移的方式來大幅度提升多線程高併發執行CAS操作的性能!

在LongAdder的底層實現中,有一個base變量,首先多個線程會CAS修改這個base變量,對其進行累加操作。但是,一旦線程併發的數量過高,就會採取分段CAS機制,也就是內部會搞一個Cell數組,每個數組單元都是一個數值分段。這時,讓大量的線程分別去對不同Cell內部的value值進行CAS累加操作,這樣就把CAS計算壓力分散到了不同的Cell分段數值中了!這樣就可以大幅度的降低多線程併發更新同一個數值時出現的無限循環的問題,大幅度提升了多線程併發更新數值的性能和效率!

而且他內部實現了自動分段遷移的機制,也就是如果某個Cell的value執行CAS失敗了,那麼就會自動去找另外一個Cell分段內的value值進行CAS操作。這樣也解決了線程空旋轉、自旋不停等待執行CAS操作的問題,讓一個線程過來執行CAS時可以儘快的完成這個操作。

最後,如果你要從LongAdder中獲取當前累加的總值,就會把base值和所有Cell分段數值加起來返回給你。

分段CAS核心源碼:

final void longAccumulate(long x, LongBinaryOperator fn,
                             boolean wasUncontended) {
       int h;
       if ((h = getProbe()) == 0) { //獲取PROBE變量,探針變量,與當前運行的線程相關,不同線程不同
           ThreadLocalRandom.current(); //初始化PROBE變量,和getProbe都使用Unsafe類提供的原子性操作。
           h = getProbe();
           wasUncontended = true;
       }
       boolean collide = false;
       for (;;) { //cas經典無限循環,不斷嘗試
           Cell[] as; Cell a; int n; long v;
           if ((as = cells) != null && (n = as.length) > 0) { // cells不爲null,並且數組size大於0
           //表示cells已經初始化了
               if ((a = as[(n - 1) & h]) == null) { //通過與操作計算出來需要操作的Cell對象的座標
                   if (cellsBusy == 0) { //volatile 變量,用來實現spinLock,來在初始化和resize cells數組時使用。
                   //當cellsBusy爲0時,表示當前可以對cells數組進行操作。 
                       Cell r = new Cell(x);//將x值直接賦值給Cell對象
                       if (cellsBusy == 0 && casCellsBusy()) {//如果這個時候cellsBusy還是0
                       //就cas將其設置爲非0,如果成功了就是獲得了spinLock的鎖.可以對cells數組進行操作.
                       //如果失敗了,就會再次執行一次循環
                           boolean created = false;
                           try {
                               Cell[] rs; int m, j;
                               //判斷cells是否已經初始化,並且要操作的位置上沒有cell對象.
                               if ((rs = cells) != null &&
                                   (m = rs.length) > 0 &&
                                   rs[j = (m - 1) & h] == null) {
                                   rs[j] = r; //將之前創建的值爲x的cell對象賦值到cells數組的響應位置.
                                   created = true;
                               }
                           } finally {
                               //經典的spinLock編程技巧,先獲得鎖,然後try finally將鎖釋放掉
                               //將cellBusy設置爲0就是釋放鎖.
                               cellsBusy = 0;
                           }
                           if (created)
                               break; //如果創建成功了,就是使用x創建了新的cell對象,也就是新創建了一個分擔熱點的value
                           continue; 
                       }
                   }
                   collide = false; //未發生碰撞
               }
               else if (!wasUncontended)//是否已經發生過一次cas操作失敗
                   wasUncontended = true; //設置成true,以便第二次進入下一個else if 判斷
               else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                            fn.applyAsLong(v, x))))
                   //fn是操作類型,如果是空,就是相加,所以讓a這個cell對象中的value值和x相加,然後在cas設置,如果成果
                  //就直接返回
                   break;
               else if (n >= NCPU || cells != as)
                 //如果cells數組的大小大於系統的可獲得處理器數量或在as不再和cells相等.
                   collide = false;            // At max size or stale
               else if (!collide)
                   collide = true;
               else if (cellsBusy == 0 && casCellsBusy()) {
                 //再次獲得cellsBusy這個spinLock,對數組進行resize
                   try {
                       if (cells == as) {//要再次檢測as是否等於cells以免其他線程已經對cells進行了操作.
                           Cell[] rs = new Cell[n << 1]; //擴容一倍
                           for (int i = 0; i < n; ++i)
                               rs[i] = as[i];
                           cells = rs;//賦予cells一個新的數組對象
                       }
                   } finally {
                       cellsBusy = 0;
                   }
                   collide = false;
                   continue;
               }
               h = advanceProbe(h);//由於使用當前探針變量無法操作成功,所以重新設置一個,再次嘗試
           }
           else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
           //cells數組未初始化,獲得cellsBusy lock,來初始化
               boolean init = false;
               try {                           // Initialize table
                   if (cells == as) {
                       Cell[] rs = new Cell[2];
                       rs[h & 1] = new Cell(x); //設置x的值爲cell對象的value值
                       cells = rs;
                       init = true;
                   }
               } finally {
                   cellsBusy = 0;
               }
               if (init)
                   break;
           }//如果初始化數組失敗了,那就再次嘗試一下直接cas base變量,如果成功了就直接返回
           else if (casBase(v = base, ((fn == null) ? v + x :
                                       fn.applyAsLong(v, x))))
               break;                          // Fall back on using base
       }
   }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章